前言: spring security的认证流程非常复杂,我也只是简单学习,通过security web项目和security在微服务中的使用,有一些理解,首先需要理解的一点就是我们可以把security看成一个过滤器链,后面我们会看到一些过滤器。
一、用户登录 (一)、UsernamePasswordAuthenticationFilter 用户登录时这个过滤器会起作用,其中有一个方法
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
这个方法会根据用户登录时输入的username和password封装成UsernamePasswordAuthenticationToken并生成Authentication并返回,需要注意的是,这个时候Authentication并没有通过认证,也就没有放到security的上下文中,接下来会调用IOC容器中实现UserDetailsService接口的类,也就是我们的自定义类,例如UserDetailsServiceImpl
(二)、UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(!"zhangsan".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
String password = passwordEncoder.encode("123456");
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("salary,ROLE_abc,bbb"));
}
}
这个类里会调用loadUserByUsername方法,通过username查询数据库中是否存在User,如果存在,接下来查询User的权限列表,之后封装成UserDetails并返回,之后security会比对Authentication和UserDetails中的密码是否一致,如果不一致,直接认证失败,如果一致,会把UserDetails中的权限给Authentication,之后把Authentication放入security的上下文,由此就完成了认证和授权。
二、用户登录之后访问 如果是单体web项目,security会把用户的登录信息存储在session中,用户再次登录的时候,直接从session中获取即可,然后进行权限校验即可; 如果是微服务项目,由于采用的是前后端分离,用session存储用户的登录信息非常的不方便,所以我们这里采用基于jwt的方式进行权限认证,登录的逻辑基本不变,唯一的区别就是认证后需要返回给前端token,同时还需要在redis中存储用户的权限信息,之后每次用户访问都需要带上token,用户带着token访问会经过下面的过滤器(需要自己实现并配置到security中生效)。
public class TokenAuthFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
public TokenAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authRequest = getAuthention(request);
if (authRequest!=null){
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request,response);
}
public UsernamePasswordAuthenticationToken getAuthention(HttpServletRequest request){
String token = request.getHeader("token");
if (token!=null){
String username = tokenManager.getUserInfoFromToken(token);
List<String> permissionsValueList = (List<String>) redisTemplate.opsForValue().get(username);
Collection<GrantedAuthority> authority = new ArrayList<>();
for (String permissionsValue : permissionsValueList) {
SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionsValue);
authority.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authority);
}
return null;
}
}
这里核心doFilterInternal方法,根据token获取用户信息,并校验token的合法性,如果合法,再在redis中查询用户的权限信息,封装成UsernamePasswordAuthenticationToken(也就是Authentication的子类)直接放入security的上下文,由此就完成了认证授权。
以上只是个人理解,理解的比较肤浅,如果有错误,肯定评论指正,多多学习!
|