SpringSecurity微服务学习(尚硅谷)
介绍
学习springsecurity的代码
地址:https://gitee.com/XuLiZhao/springsecurity.git
项目模块
- common模块:用来存放项目中的公共资源
- 子模块service_base:项目中的一些公共类,如异常处理类,工具类等等。
- 子模块spring_security:项目的安全认证模块,配置项目的安全框架
- infrastructure模块:项目的基础结构,包含网关等配置
- 子模块api_gateway:配置项目的网关
- service模块:用户服务的相关配置
使用说明
- 项目中需要使用到nacos和redis
- 这个项目主要是配合学习SpringSecurity来学习的,所以我主要分析SpringSecurity+JWT认证相关过程。
- 用户登录的请求地址在认证过滤器中指定了,访问即可
SpringSecurity+JWT
依赖
<!-- Spring Security依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--JWT依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> <!--hutool工具类--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.3</version> </dependency>
工具类
密码处理:
DefaultPasswordEncoder
@Component public class DefaultPasswordEncoder implements PasswordEncoder { public DefaultPasswordEncoder() { this(-1); } public DefaultPasswordEncoder(int strength) { } //进行MD5加密 @Override public String encode(CharSequence charSequence) { return SecureUtil.md5(charSequence.toString()); } //进行密码的比对 @Override public boolean matches(CharSequence charSequence, String encodePassword) { return encodePassword.equals(SecureUtil.md5(charSequence.toString())); } }
JWT工具类类:
TokenManager
@Component public class TokenManager { /** *设置token有效时长 */ private final long tokenExpiration = 24*60*60*1000; /** *编码密钥 */ private String tokenSignKey = "123456"; /** *使用JWT根据用户名生成token */ public String createToken(String username){ String token = Jwts.builder().setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) .signWith(SignatureAlgorithm.HS512,tokenSignKey).compressWith(CompressionCodecs.GZIP).compact(); return token; } /** *根据token字符串得到用户信息 */ public String getUserInfoFromToken(String token){ String userInfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject(); return userInfo; } /** *删除token */ public void removeToken(String token){ } }
退出处理器:
TokenLogoutHandler
public class TokenLogoutHandler implements LogoutHandler { private TokenManager tokenManager; private RedisTemplate redisTemplate; public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) { this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; } @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { //从header中获取token String token = request.getHeader("token"); //token不为空,移除token,从redis中删除 if (ObjectUtil.isNotNull(token)){ //移除 tokenManager.removeToken(token); //从token中获取用户名 String username = tokenManager.getUserInfoFromToken(token); redisTemplate.delete(username); } ResponseUtil.out(response, R.ok()); } }
未授权处理类:
UnauthEntryPoint
public class UnauthEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { //返回未授权的相关信息 ResponseUtil.out(httpServletResponse, R.error()); } }
过滤器
认证过滤器:
TokenLoginFilter
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private TokenManager tokenManager; private RedisTemplate redisTemplate; private AuthenticationManager authenticationManager; public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) { this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; this.authenticationManager = authenticationManager; this.setPostOnly(false); //设置登录路径 this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST")); } /** *获取表单提交用户名和密码 */ @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(); } } /** *认证成功调用的方法 */ @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()); 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()); } }
授权过滤器:
TokenAuthFilter
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 (ObjectUtil.isNotNull(authRequest)){ SecurityContextHolder.getContext().setAuthentication(authRequest); } chain.doFilter(request,response); } public UsernamePasswordAuthenticationToken getAuthention(HttpServletRequest request){ //从header中获取token String token = request.getHeader("token"); if (ObjectUtil.isNotNull(token)){ String username = tokenManager.getUserInfoFromToken(token); //从redis中获取权限列表信息 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; } }
核心配置类
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter { private TokenManager tokenManager; private RedisTemplate redisTemplate; private DefaultPasswordEncoder defaultPasswordEncoder; private UserDetailsService userDetailsService; @Autowired public TokenWebSecurityConfig(TokenManager tokenManager, RedisTemplate redisTemplate, DefaultPasswordEncoder defaultPasswordEncoder, UserDetailsService userDetailsService) { this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; this.defaultPasswordEncoder = defaultPasswordEncoder; this.userDetailsService = userDetailsService; } /** * 配置设置 */ //设置退出的地址和 token,redis 操作地址 @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(tokenManager, redisTemplate, authenticationManager())) //添加认证过滤器 .addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic(); //添加授权过滤器 } /** *调用userDetailService和密码处理 */ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder); } /** *不进行认证的路径,可以直接访问 */ @Override public void configure(WebSecurity web) throws Exception{ web.ignoring().antMatchers("/api/**"); } }
UserDetailsService实现类
实现用户认证逻辑
@Service("userDetailsService") public class UserDetailServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Autowired private PermissionService permissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名查询数据库 User user = userService.selectByUsername(username); //判断 if (ObjectUtil.isNull(user)){ throw new UsernameNotFoundException("用户不存在"); } com.atguigu.security.entity.User curUser = new com.atguigu.security.entity.User(); BeanUtils.copyProperties(user,curUser); //根据用户查询出用户列表信息 List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId()); SecurityUser securityUser = new SecurityUser(); securityUser.setPermissionValueList(permissionValueList); return securityUser; } }