花了点时间写了一个SpringSecurity集合JWT完成身份验证的Demo,并按照自己的想法完成了动态权限问题。在写这个Demo之初,使用的是SpringSecurity自带的注解权限,但是这样权限就显得不太灵活,在实现之后,感觉也挺复杂的,欢迎大家给出建议。Demo下载地址,见文末。
JWT是什么可以参考我另一篇博文:浅谈JWT身份认证及其优缺点
认证流程及授权流程
我画了个建议的认证授权流程图,后面会结合代码进行解释整个流程。
一、登录认证阶段
实现SpringSecurity的UsernamePasswordAuthenticationFilter
接口(public class TokenLoginFilter
extends UsernamePasswordAuthenticationFilter),在它的实现类的构造方法
里设置登录的请求路径和请求方式。
this.setPostOnly(false);
// 认证路径 - 发送什么请求,就会进行认证
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/service_auth/admin/index/login","POST"));
当前端发起配置的请求时,请求会被拦截,进入到attemptAuthentication
方法进行验证,在这个方法里可以从request
中取出账号、密码,从而调用AuthenticationManager
的authenticate
去校验账号、密码是否正确。
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
// 也可以直接获取账号密码
String username = obtainUsername(request);
String password = obtainPassword(request);
log.info("TokenLoginFilter-attemptAuthentication:尝试认证,用户名:{}, 密码:{}", username, password);
// 在authenticate里去进行校验的,校验过程中会去把UserDetailService里返回的SecurityUser(UserDetails)里的账号密码和这里传的账号密码进行比对
// 并在UserDetailService里将权限进行赋予
// 校验通过,会进入到successfulAuthentication方法
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
那么这个authenticate
方法是怎么验证我们账号密码正确性的呢?
打上断点,跟随源码,我们进入到authenticate
方法内部:
然后进入这个方法内部,继续往下走,看到一段核心代码:
进入retrieveUser
方法里,然后往下走,看到一句核心代码,这个核心代码就是获取用户信息的:
这里注意,调用了UserDetailsService
的loadUserByUsername
方法,传入的就是前端传过来的username
,意思就是要根据这个username
去获取UserDetails
对象,所以我们就要去查询数据库
,所以我们就要实现UserDetailsService
接口并重写loadUserByUsername
方法。
@Service("userDetailsService")
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("根据username去数据库查询用户信息,username:{}", username);
// 1、从数据库中取出用户信息 - 这里模拟,直接new一个User对象
User user = new User();
user.setUsername(username);
// 111111经过加密后
user.setPassword("96e79218965eb72c92a549dd5a330112");
SecurityUser securityUser = new SecurityUser(user);
// 可以根据查出来的user.getId()去查询这个用户对应的权限集合 - 这里模拟,直接new一个结合
List<String> authorities = new ArrayList<>();
// 将权限赋予用户
securityUser.<