一、处理用户信息获取逻辑
用户信息的获取逻辑,在SpringSecurity中是被封装在一个接口后面的,这个接口叫UserDetailsService。这个接口中只有一个方法loadUserByUsername,接收一个String类型的参数,返回一个UserDetails对象。这个方法的作用就是根据用户在前台输入的用户名到数据集合(数据库)中去读取一个用户信息,用户信息最后被封装在一个UserDetails的接口的实现类中。然后SpringSecurity会拿着这个用户信息去做相应的处理和校验,如果校验都通过了就会把用户放在session中,那么用户认证就成功了。如果认证失败就会抛出UsernameNotFoundException异常,然后SpringSecurity捕获到这个异常会显示出相应的信息。
1.自定义认证逻辑
首先我需要新建一个MyUserDetailsService类去实现UserDetailsService接口,重写里面的loadUserByUsername方法,其实在实际的开发过程中应该是要去service层去读取数据库中根据String类型的用户名去查询相应的用户信息的。但是在这我就简写了。
@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据用户名查找用户信息
LOGGER.info("用户名:"+s);
return new User(s,"123456",
true,true,true,true,//账户是否被锁定
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
然后我们启动服务,在密码框中输入“123456”就会进入到系统rest服务中。
二、处理用户校验逻辑
在用户校验逻辑中,分为两部分,第一个是用户名的密码校验逻辑;第二是其他的一些校验(用户是否被冻结、是否过期等)。
我们先来看UserDetails这个接口的源代码:
UserDetails源代码
- getAuthorities是用来获取用户权限信息的
- getPassword获取密码
- getUsername获取用户名
除了这三个方法之外,下面还有四个返回布尔值的方法,这四个方法就是用来让我们自己去执行校验逻辑的。我们需要将我们自己的校验的结果通过这些方法的实现去返回回去。 - isAccountNonExpired表示账户没有过期,返回true表示认证成功,返回false代表过期了。如果在系统中没有关于这个的逻辑,可以永远返回true
- isCredentialsNonExpired指密码是否过期,因为有些安全级别比较高的网站可能会要求用户三十天或固定时间去修改密码。这个方法可以告诉SpringSecurity密码是否过期了。
- isAccountNonLocked账户是否被锁定,用来表示这个账户是否被冻结了,
- isEnabled账户是否可用,表示用户是否被删除了(注销)
isAccountNonLocked和isEnabled的区别是isAccountNonLocked可以恢复使用的,但是isEnabled被注销的用户一般是不能被恢复的。
在MyUserDetailsService这个类中重写的那个方法里面我们可以自定义哪些属性是false,一旦有一个设置为false,那么我的校验逻辑是不能被通过的。比如我在账户是否锁定的字段设置为false,则会有相应的错误处理
@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据用户名查找用户信息
LOGGER.info("用户名:"+s);
//根据查找到的用户信息查找用户是否被冻结
return new User(s,"123456",
true,true,true,false,//账户被锁定
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
然后在浏览器中输入资源地址,即使输入正确的用户名和密码,也会给出相应的提示
用户账号已被锁定
三、处理密码加密逻辑
在我们的系统应用中,我们不会将用户的明文写入到数据库中,也就是说从数据库中取出来的密码应该是已经加密过的一串字符串,在SpringSecurity中处理加密解密的是一个PasswordEncoder(注意:是org.springframework.security.crypto.password包下的)。
在这个接口中有两个方法:
public interface PasswordEncoder {
/**
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* they do not. The stored password itself is never decoded.
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
}
一个是encode加密方法,这个方法应该是插入数据库之前去调用的方法。
另一个是matches匹配方法,返回一个布尔值。这个方法是SpringSecurity去调用的,在拿到UserDetails对象之后,会根据UserDetails中的password跟用户在登录请求中输入的密码去进行匹配,如果匹配上了就会返回true。
然后我们去进行密码的加密操作
package com.tinner.security.browser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据用户名查找用户信息
LOGGER.info("用户名:"+s);
//根据查找到的用户信息查找用户是否被冻结
String encodingPass = passwordEncoder.encode("123456");
LOGGER.info("数据库中的用户名密码是:"+encodingPass);
return new User(s,encodingPass,
true,true,true,true,//账户被锁定
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
最后在打印出来的日志中我们可以看到,虽然用户每次输入“123456”,但是在控制台中打印出来的加密后的字符串每次都是不一样的,其实就是根据这个字符串和用户在输入框中输入的密码去进行比对的。为什么每次都不一样,就是SpringSecurity在对密码进行加密的强大之处,它在每次加密之前都会随机生成一个“盐”并且在生成随机字符串的时候都会讲盐混入到字符串中,然后再反推回来解密后的密码去进行匹配。这样就大大提高了系统用户信息的安全性。
网友评论