JWT|单点登录解决方案|Spring Boot使用Redis如何实现Session共享

本文探讨了传统Web应用的身份验证和JWT(JSON Web Token)的原理与区别,详细介绍了JWT的使用场景、流程和数据结构。同时,讨论了JWT的问题与趋势。针对单点登录(SSO)需求,指出了传统Session在分布式架构中的局限性,并提出Spring Boot结合Redis实现Session共享的解决方案。最后,提到了CAS(Central Authentication Service)作为开源SSO的实现,以及在服务端和客户端的配置方法。
摘要由CSDN通过智能技术生成

传统web应用身份验证

1.用户向服务器发送用户名和密码。
2.验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。
3.服务器向用户返回session_id,session信息都会写入到用户的Cookie。
4.用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
5.服务器收到session_id并对比之前保存的数据,确认用户的身份。
缺点:没有分布式架构,无法支持横向扩展。

JWT初识
1.概念

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。

通过客户端保存数据,而服务器根本不保存会话数据,每个请求都被发送回服务器。
JWT是这种解决方案的代表。
用JSON对象在各方之间安全地传输信息。
该信息可以被验证和信任,因为它是数字签名的。
:<><><<><><>>JWT与Session相同点:<><><<><><>>

它们都是存储用户信息

:<><><<><><>>不同:<><><<><><>>

Session是在服务器端的,而JWT是在客户端的。
Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

2.应用场景
  • Authorization (授权) :
    这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
  • Information Exchange (信息交换) :
    对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
3.流程图解

在这里插入图片描述

  1. 应用(或者客户端)想授权服务器请求授权。例如,如果用授权码流程的话,就是/oauth/authorize
  2. 当授权被许可以后,授权服务器返回一个access token给应用,将其存储在Cookie或localStorage中。
  3. 客户端将在与服务器交互中都会带JWT-access-token。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。
4. 数据结构

JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
Header     Payload     Signature
因此,一个典型的JWT看起来是这个样子的:
xxxxx.yyyyy.zzzzz

5. JWT问题和趋势

1、JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。
2、当JWT未加密方法是,一些私密数据无法通过JWT传输。
3、JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
4、JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。
5、JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。 为了减少盗用,JWT的有效期不宜设置太长。 对于某些重要操作

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,针对你的问题,我会给出一份 Java 微服务项目:家具商城的单点登录功能实现代码流程,使用 Spring Security+JWT+Redis 完成单点登录。代码实现流程如下: 1. 添加依赖 在 pom.xml 文件中添加以下依赖: ``` <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 2. 配置 Spring Security 在 Spring Security 配置类中,添加以下配置: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Autowired private JwtTokenUtil jwtTokenUtil; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/auth/**").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } @Bean public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationTokenFilter(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 3. 实现 JWT 工具类 实现 JwtTokenUtil 工具类,用于生成和解析 JWT Token。 ``` @Component public class JwtTokenUtil { private static final String SECRET = "secret"; private static final String ISSUER = "issuer"; private static final String AUDIENCE = "audience"; private static final String CLAIM_KEY_USERNAME = "sub"; private static final String CLAIM_KEY_CREATED = "created"; private static final long EXPIRATION_TIME = 86400000; // 24 hours public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); claims.put(CLAIM_KEY_CREATED, new Date()); return Jwts.builder() .setClaims(claims) .setIssuer(ISSUER) .setAudience(AUDIENCE) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); } public String getUsernameFromToken(String token) { String username; try { final Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } public Date getExpirationDateFromToken(String token) { Date expiration; try { final Claims claims = getClaimsFromToken(token); expiration = claims.getExpiration(); } catch (Exception e) { expiration = null; } return expiration; } private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } catch (Exception e) { claims = null; } return claims; } public boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } } ``` 4. 实现认证过滤器 实现 JwtAuthenticationTokenFilter 过滤器,用于在请求头中解析 JWT Token,并进行认证。 ``` public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String authToken = authHeader.substring("Bearer ".length()); String username = jwtTokenUtil.getUsernameFromToken(authToken); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } } chain.doFilter(request, response); } } ``` 5. 实现单点登录功能 在登录接口中,使用 JWT 工具类生成 JWT Token,并将 Token 存储到 Redis 中,设置 Token 过期时间,并返回 Token 给客户端。 ``` @RestController @RequestMapping("/auth") public class AuthController { @Autowired private UserService userService; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private RedisTemplate<String, Object> redisTemplate; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { User user = userService.findByUsername(loginRequest.getUsername()); if (user == null) { return ResponseEntity.badRequest().body(new ApiResponse(false, "Invalid username")); } if (!user.getPassword().equals(loginRequest.getPassword())) { return ResponseEntity.badRequest().body(new ApiResponse(false, "Invalid password")); } UserDetails userDetails = new User(user.getUsername(), user.getPassword(), new ArrayList<>()); String token = jwtTokenUtil.generateToken(userDetails); redisTemplate.opsForValue().set(token, userDetails.getUsername(), jwtTokenUtil.EXPIRATION_TIME, TimeUnit.MILLISECONDS); return ResponseEntity.ok(new JwtAuthenticationResponse(token)); } } ``` 6. 实现单点登录拦截器 在请求过程中,使用 JwtAuthenticationInterceptor 拦截器,从请求头中解析 JWT Token,并从 Redis 中获取 Token 对应的用户名,将用户名设置到请求属性中,供后续业务使用。 ``` @Component public class JwtAuthenticationInterceptor implements HandlerInterceptor { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String authToken = authHeader.substring("Bearer ".length()); String username = (String) redisTemplate.opsForValue().get(authToken); if (username != null && !jwtTokenUtil.isTokenExpired(authToken)) { request.setAttribute("username", username); return true; } } response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } } ``` 7. 配置 Redis 在 application.properties 中,添加以下配置: ``` spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= spring.redis.database=0 ``` 至此,Java 微服务项目:家具商城的单点登录功能使用 Spring Security+JWT+Redis 完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值