常用登录加密之Shiro与Spring Security的使用对比

        Shiro与Spring Security都是主流的身份认证和权限控制安全框架,Shiro偏向于前后端不分离平台,而Spring Security更偏向于前后端分离平台。接下来简单列一下两种登录验证的执行流程和示例,了解实际运用中的登录执行流程,然后重点剖析一下密码验证的过程。其实,密码验证的本质就是比较用户输入的凭证(密码)和存储的凭证(加密后的密码)是否匹配 ,如果一致,则表示密码验证通过。

Spring Security的登录过程

1.导入依赖

		<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
        </dependency>

2.UsernamePasswordAuthenticationToken

在登录方法中通过UsernamePasswordAuthenticationToken把用户名、密码信息封装起来,使用AuthenticationManager的authenticate方法并传入UsernamePasswordAuthenticationToken对象

@RestController
public class LoginController {
    
    

    @Resource
    private AuthenticationManager authenticationManager;

    @RequestMapping("/login")
    public R login(String userName, String password){
    
    
        Authentication authentication = null;
        try {
    
    
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return R.error();
        }
        //登录认证成功,生成token返回
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        String token = getToken(loginUser); 
        return R.ok().setData(token);
    }

    private String getToken(LoginUser loginUser) {
    
    
        //TODO 按照自己的业务需求生成
        return null;
    }
}

上下文对象,用来存放身份认证信息

/**
 * 上下文对象,用来存放身份认证信息
 */
public class AuthenticationContextHolder {
    
    
    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

    public static Authentication getContext() {
    
    
        return contextHolder.get();
    }

    public static void setContext(Authentication context) {
    
    
        contextHolder.set(context);
    }

    public static void clearContext() {
    
    
        contextHolder.remove();
    }
}

3.UserDetailsServiceImpl

①同时定义UserDetailsServiceImpl类实现UserDetailsService,并重写loadUserByUsername方法,上面步骤就会执行到此方法中;
②在loadUserByUsername方法中通过上下文传值AuthenticationContextHolder获取到登录的用户名和密码;
③进行账号、密码验证

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        User user = userService.selectUserByUserName(username);
        boolean state = validate(user); //验证用户信息
        if (!state) {
    
    
            //账号、密码验证失败
            throw new UsernameNotFoundException("账号、密码验证失败");
        }
        //生成UserDetails信息返回
        UserDetails userDetails = new LoginUser();
        return userDetails;
    }

    private boolean validate(User user){
    
    
        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
        String password = usernamePasswordAuthenticationToken.getCredentials().toString();
        //验证账号、密码
        return new BCryptPasswordEncoder().matches(password, user.getPassword());
    }
}

4.账号密码验证说明(重点)

加密说明

Spring Security主要使用BCryptPasswordEncoder进行加密的,BCryptPasswordEncoder用SHA-256+随机盐+密钥对密码进行加密,这种加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。BCryptPasswordEncoder的加密过程中,同一个密码,由于盐是随机的,所以每次加密的结果都是不一样的,然后就是明文密码的hash值与数据库中存储密码hash值进行比较。

加密特点:不需要提供盐、每次加密结果都不一样
加密示例
	public static void main(String[] args) {
    
    
        //初始化BCryptPasswordEncoder
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        //原始密码
        String initPwd = "123456";
        String encode1 = encoder.encode(initPwd);
        System.out.println("1次加密结果:"+encode1);
        String encode2 = encoder.encode(initPwd);
        System.out.println("2次加密结果:"+encode2);
        String encode3 = encoder.encode(initPwd);
        System.out.println("3次加密结果:"+encode3);
        boolean b1 = encoder.matches(initPwd, encode1);
        System.out.println("1次加密认证:"+b1);
        boolean b2 = encoder.matches(initPwd, encode2);
        System.out.println("2次加密认证:"+b2);
        boolean b3 = encoder.matches(initPwd, encode3);
        System.out.println("3次加密认证:"+b3);
    }

运行结果:

1次加密结果:$2a$10$8NfgZSeUG8mCApNeJumsg.Z6k3SF1HrGPB0FPuLDlFy2jYF.uHYAm
2次加密结果:$2a$10$.PTFbG0qwMHiQLgA5SY/pOOcHCUP7gZQvQYAEv6RRaE0R3Wc9e.hW
3次加密结果:$2a$10$YFI4vgXpnYlcfsqpplqJ1OjL8JczZSqYWjL0jIkmggTpe7cSuaaNi
1次加密认证:true
2次加密认证:true
3次加密认证:true



Shiro的登录过程

1.导入依赖

		<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-crypto-hash</artifactId>
            <version>1.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.6.0</version>
        </dependency>

2.UsernamePasswordToken

通过UsernamePasswordToken这个类会将用户登录信息封装起来,生成Token,然后通过SecurityUtils下的subject传入token执行登录方法

@RestController
public class LoginController {
    
    

    @RequestMapping("/login")
    public R login(String userName, String password){
    
    
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        Subject subject = SecurityUtils.getSubject();
        try {
    
    
            subject.login(token);
            //验证通过,登录成功
            return R.ok();
        } catch (AuthenticationException e) {
    
    
            e.printStackTrace();
            //用户或密码错误,验证失败
            return R.error();
        }
    }
}

3.UserRealm

①认证器会将Token分解开来,分成账号和密码,并通过Relam这个桥梁向数据库进行求证;
②然后自定义UserRealm类并继承AuthorizingRealm方法,重写doGetAuthenticationInfo方法中就可以从token中获取用户账号、密码信息;
③进行账号、密码验证

public class UserRealm extends AuthorizingRealm {
    
    

    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        //授权方法 TODO
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        //登录方法
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String userName = upToken.getUsername();
        String password = new String(upToken.getPassword());
        //执行登录验证
        User user = null;
        try {
    
    
            //验证账号、密码
            user = validate(userName, password);
        } catch (Exception e) {
    
    
            throw new AuthenticationException(e.getMessage(), e);
        }
        return new SimpleAuthenticationInfo(user, password, getName());
    }

    private User validate(String userName, String password) throws AuthenticationException {
    
    
        User user = userService.selectUserByUserName(userName);
        if (user == null) {
    
    
            throw new AuthenticationException("用户不存在");
        }
        //输入密码加密
        String inputPwd = encryptPassword(userName, password, user.getSalt());
        //数据库密码
        String dbPwd = user.getPassword();
        if (!inputPwd.equals(dbPwd)) {
    
    
            throw new AuthenticationException("用户名、密码验证失败");
        }
        return user;
    }

    private static String encryptPassword(String userName, String password, String salt) {
    
    
        return new Md5Hash(userName + password + salt).toHex().toString();
    }
}

4.账号密码验证说明(重点)

加密说明

Shiro主要使用的是MD5进行加密的,底层就是传入登录名、密码、盐,使用MD5进行hash加密计算,得到一个密文字符串,通常会把盐和这个密文字符串存入到数据库中。下次用户登录的时候,传入登录名和密码,从数据库中获取到盐,MD5加密生成此时的加密密文,与数据库中存储的加密密文进行匹配。如果一致则验证通过,如果不一致,则验证不通过。

加密特点:需要提供盐、加密的时候需要传入登录用户名、加密结果都是一样的
加密示例
	public static void main(String[] args) {
    
    
        //密文密码
        String dbPwd = "3d3e2e119996cedb7401025cced5c1b0";
        //用户名
        String userName = "admin";
        //盐
        String salt = "111111";
        //明文密码
        String inputPwd = "123456";
        String encryptPassword = encryptPassword(userName, inputPwd, salt);
        System.out.println("加密结果:"+encryptPassword);
        boolean b = dbPwd.equals(encryptPassword);
        System.out.println("认证结果:"+b);
    }
    
	public static String encryptPassword(String userName, String password, String salt) {
    
    
        return new Md5Hash(userName + password + salt).toHex().toString();
    }

运行结果:

加密结果:3d3e2e119996cedb7401025cced5c1b0
认证结果:true

猜你喜欢

转载自blog.csdn.net/weixin_50989469/article/details/135047736
今日推荐