SpringBoot整合SpringSecurity和JWT和Redis实现统一鉴权认证
作者:放风讲故事
一、介绍
Spring Security是一个强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的实际标准。Spring Security是一个可以为Java应用程序提供全面安全服务的框架。同时,它也可以轻松扩展以满足自定义需求。
二、主要功能
Authentication (认证),就是用户登录
Authorization (授权):一旦身份验证成功,判断用户拥有什么权限,可以访问什么资源
防止跨站请求伪造(CSRF):Spring Security提供了内置的防护机制,可以防止跨站请求伪造攻击。
密码存储:Spring Security提供了多种密码存储格式,包括明文、加密和哈希。
集成其他安全框架:Spring Security可以与其他安全框架如OAuth2、JWT等进行集成,以提供更全面的安全解决方案。
三、原理
SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。
1. SpringSecurity 过滤器链
SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。
- SecurityContextPersistenceFilter:每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中。
- LogoutFilter:用于处理退出登录。
- UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。
- BasicAuthenticationFilter:检测和处理 http basic 认证。
- ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。
- FilterSecurityInterceptor:负责权限校验的过滤器,可以看做过滤器链的出口。
- …
流程说明:客户端发起一个请求,进入 Security 过滤器链。
1.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
2.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
3.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。
2. JWT校验登录的校验流程
首先前端一样是把登录信息发送给后端,后端查询数据库校验用户的账号和密码是否正确,正确的话则使用jwt生成token,并且返回给前端。以后前端每次请求时,都需要携带token,后端获取token后,使用jwt进行验证用户的token是否无效或过期,验证成功后才去做相应的逻辑。
四、Spring Boot整合Redis、SpringSecurity、JWT的示例demo
- 添加依赖项在
pom.xml
文件中添加以下依赖项:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies>`
- 创建Redis配置类
@Configuration public class RedisConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Bean public JedisConnectionFactory jedisConnectionFactory() { return new JedisConnectionFactory(new RedisStandaloneConfiguration(host, port)); } @Bean public RedisTemplate<String, Object> redisTemplate() { final RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(jedisConnectionFactory()); return template; } }
- 创建
JwtTokenUtil
类,用于生成和验证JWT令牌。
@Component public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = -2550185165626007488L; private static final String secret = "mySecret"; public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS512, secret).compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }`
- 创建
JwtAuthenticationEntryPoint
类,用于处理未经授权的请求。
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -7858869558953243875L; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }`
- 创建
JwtRequestFilter
类,用于解析和验证JWT令牌。
@Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private MyUserDetailsService myUserDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { logger.warn("JWT Token does not begin with Bearer String"); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } }`
- 配置Spring Security
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private UserDetailsService jwtUserDetailsService; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf().disable() .authorizeRequests().antMatchers("/authenticate").permitAll(). anyRequest().authenticated().and(). exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } }`
以上是简单的Spring Boot整合Redis、Security、JWT和Redis的示例,可以根据自己的实际需求进行调整。更多相关SpringBoot 统一鉴权认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!