认证过滤器:通过attemptAuthentication方法进行认证,获取请求的账号密码为user对象,通过AuthenticationManager的authenticate方法,使用自己编写的UserDetailService进行认证,认证成功则调用successfulAuthentication方法,将用户的权限存储在redis中,返回token
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;
private TokenManager tokenManager;
public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
this.redisTemplate = redisTemplate;
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.setPostOnly(false);//只接受POST请求(否则调用认证失败方法)
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login", "POST"));
}
//获取表单提交用户名密码
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//获取表单数据
User user = null;
try {
user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
//认证成功
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//认证成功后用户信息
SecurityUser user = (SecurityUser) authResult.getPrincipal();
String username = tokenManager.generateToken(user.getCurrentUserInfo().getUsername());
String token = tokenManager.generateToken(username);
redisTemplate.opsForValue().set(username, user.getPermissionValueList());
ResponseUtil.out(response, R.ok().data("token", token));
}
//认证失败
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
Security配置类
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private RedisTemplate redisTemplate;
private TokenManager tokenManager;
private DefaultPasswordEncoder defaultPasswordEncoder;
private UserDetailsService userDetailsService;
@Autowired
public TokenWebSecurityConfig(RedisTemplate redisTemplate,TokenManager tokenManager,DefaultPasswordEncoder defaultPasswordEncoder, UserDetailsService userDetailsService){
this.redisTemplate = redisTemplate;
this.tokenManager = tokenManager;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthEntryPoin())//没有权限访问
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate))
.and().addFilter(new TokenAuthFilter(authenticationManager() ,redisTemplate,tokenManager))
.addFilter(new TokenLoginFilter(tokenManager,redisTemplate,authenticationManager())).httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**");//不进行认证的路径,可以直接访问
}
}
密码编码译码器
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder(){
this(-1);
}
public DefaultPasswordEncoder(int i){
}
@Override
public String encode(CharSequence var1) {
return MD5.encrypt(var1.toString());
}
//和密码比对
//var1为密码,var2位加密后的密码(MD5是不可逆的)
@Override
public boolean matches(CharSequence var1, String var2) {
return var2.equals(MD5.encrypt(var1.toString()));
}
}
Token管理器
@Component
public class TokenManager {
//有效时长
private long expiraTime = 24*60;
//编制密钥
private String tokenSecretKey = "123456";
//根据用户名生成token
public String generateToken(String username){
String token = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()+expiraTime))
.signWith(SignatureAlgorithm.ES512,tokenSecretKey)
.compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//根据token获取用户信息
public String getUserInfoByToken(String token){
String userInfo = Jwts.parser().setSigningKey(tokenSecretKey)
.parseClaimsJws(token).getBody()
.getSubject();
return userInfo;
}
public void removeToken(String token){
}
}
无权访问入口
public class UnauthEntryPoin implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}
登出处理器:获取请求头携带的token,移除token,redis删除token对应的用户
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
this.tokenManager = tokenManager;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//从header获取token
String token = request.getHeader("token");
// token不为空 移除token
if(null != token){
tokenManager.removeToken(token);
String userInfo = tokenManager.getUserInfoByToken(token);
redisTemplate.delete(userInfo);
}
ResponseUtil.out(response, R.ok());
}
}
授权过滤器:根据请求头的token获取权限列表,生成一个授权对象,如果对象不为空,则放在上下文对象SecurityContext中,他是全局生效。对于@Security放在控制器方法的注解,会根据权限进行验证
public class TokenAuthFilter extends BasicAuthenticationFilter {
private RedisTemplate redisTemplate;
private TokenManager tokenManager;
public TokenAuthFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate, TokenManager tokenManager) {
super(authenticationManager);
this.redisTemplate = redisTemplate;
this.tokenManager = tokenManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取当前认证的用户权限信息
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
//如果有权限信息,就放在权限上下文
if(authentication != null){
SecurityContextHolder.getContext().setAuthentication(authentication);
}
//放行
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("token");
if (token != null) {
//从token获取用户名
String username = tokenManager.getUserInfoByToken(token);
//从redis获取权限列表
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(username);
Collection<GrantedAuthority> authrity = new ArrayList<>();
for (String s : permissionValueList) {
authrity.add(new SimpleGrantedAuthority(s));
}
return new UsernamePasswordAuthenticationToken(username,token,authrity);
}
return null;
}
}
|