SpringSecurity - JWT Token微服务认证方案 SSO
微服务权限认证方案:SpringBoot + SpringSecurity + JWT
如果系统的模块众多,每个模块都需要进行授权认证,所以我们选择基于Token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为Key,权限列表为Value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,SpringSecurity解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis获取权限列表,这样SpringSecurity就能够判断当前请求是否有前权限访问。
权限管理数据模型:
认证过滤器重写:
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private TokenManager tokenManager; private RedisTemplate redisTemplate; private AuthenticationManager authenticationManager; public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) { this.authenticationManager = authenticationManager; this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; this.setPostOnly(false); this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST")); } //1 获取表单提交用户名和密码 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //获取表单提交数据 try { User 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(); } } //2 认证成功调用的方法 @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { //认证成功,得到认证成功之后用户信息 SecurityUser user = (SecurityUser)authResult.getPrincipal(); //根据用户名生成token String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername()); //把用户名称和用户权限列表放到redis redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList()); //返回token ResponseUtil.out(response, R.ok().data("token",token)); } //3 认证失败调用的方法 protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { ResponseUtil.out(response, R.error()); } }
权限过滤器重写;
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; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { //获取当前认证成功用户权限信息 UsernamePasswordAuthenticationToken authRequest = getAuthentication(request); //判断如果有权限信息,放到权限上下文中 if(authRequest != null) { SecurityContextHolder.getContext().setAuthentication(authRequest); } chain.doFilter(request,response); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { //从header获取token String token = request.getHeader("token"); if(token != null) { //从token获取用户名 String username = tokenManager.getUserInfoFromToken(token); //从redis获取对应权限列表 List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username); Collection<GrantedAuthority> authority = new ArrayList<>(); for(String permissionValue : permissionValueList) { SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue); authority.add(auth); } return new UsernamePasswordAuthenticationToken(username,token,authority); } return null; } }
tokenManager:
@Component public class TokenManager { //token有效时长 private long tokenEcpiration = 24*60*60*1000; //编码秘钥 private String tokenSignKey = "123456"; //1 使用jwt根据用户名生成token public String createToken(String username) { String token = Jwts.builder().setSubject(username) .setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration)) .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact(); return token; } //2 根据token字符串得到用户信息 public String getUserInfoFromToken(String token) { String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject(); return userinfo; } //3 删除token public void removeToken(String token) { } }
SpringSecurity 配置:
@Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .authenticationEntryPoint(new UnauthEntryPoint())//没有权限访问 .and().csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and().logout().logoutUrl("/admin/acl/index/logout")//退出路径 .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and() .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)) .addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic(); }
本方案借助 Redis 存储了用户的authorities,各个微服务可继承此SpringSecurity公共模块引入权限认证,专注于业务实现即可。
本方案微服务网关可只做转发,不做权限判定,权限由各个微服务模块自行判定。
参考微服务架构图:
参考:
https://www.bilibili.com/video/BV15a411A7kP
posted on 2021-11-15 10:50 TrustNature 阅读(348) 评论(0) 编辑 收藏 举报