SpringBoot+SpringSecurity 前后端分离 + Jwt 的权限认证

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
全局流量管理 GTM,标准版 1个月
简介:   前言  一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还让你配置登出页,错误页。  但是现在前后端分离才是正道,前后端分离的话,那就需要将返回的页面换成Json格式交给前端处理了  SpringSecurity默认的是采用Session来判断请求的用户是否登录的,但是不方便分布式的扩展,虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态,不过现在分布式的还是无状态的Jwt比较主流。

  前言

  一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还让你配置登出页,错误页。

  但是现在前后端分离才是正道,前后端分离的话,那就需要将返回的页面换成Json格式交给前端处理了

  SpringSecurity默认的是采用Session来判断请求的用户是否登录的,但是不方便分布式的扩展,虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态,不过现在分布式的还是无状态的Jwt比较主流。

  所以下面说下怎么让SpringSecurity变成前后端分离,采用Jwt来做认证的

  一、五个handler一个filter两个User

  5个handler,分别是

  实现AuthenticationEntryPoint接口,当匿名请求需要登录的接口时,拦截处理

  实现

  AuthenticationSuccessHandler接口,当登录成功后,该处理类的方法被调用

  实现

  AuthenticationFailureHandler接口,当登录失败后,该处理类的方法被调用

  实现AccessDeniedHandler接口,当登录后,访问接口没有权限的时候,该处理类的方法被调用

  实现LogoutSuccessHandler接口,注销的时候调用

  1.1 AuthenticationEntryPoint

  匿名未登录的时候访问,遇到需要登录认证的时候被调用

  /**

  * 匿名未登录的时候访问,需要登录的资源的调用类

  * @author zzzgd

  */

  @Component

  publicclassCustomerAuthenticationEntryPointimplementsAuthenticationEntryPoint{

  @Override

  public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

  //设置response状态码,返回错误信息等

  ...

  ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.REQUIRED_LOGIN_ERROR));

  }

  }

  1.2 AuthenticationSuccessHandler

  这里是我们输入的用户名和密码登录成功后,调用的方法

  简单的说就是获取用户信息,使用JWT生成token,然后返回token

  /**

  * 登录成功处理类,登录成功后会调用里面的方法

  * @author Exrickx

  */

  @Slf4j

  @Component

  publicclassCustomerAuthenticationSuccessHandlerimplementsAuthenticationSuccessHandler{

  @Override

  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

  //简单的说就是获取当前用户,拿到用户名或者userId,创建token,返回

  log("登陆成功...");

  CustomerUserDetails principal=(CustomerUserDetails) authentication.getPrincipal;

  //颁发token

  Map emptyMap=new HashMap<>(4);

  emptyMap.put(UserConstants.USER_ID,principal.getId);

  String token=JwtTokenUtil.generateToken(principal.getUsername, emptyMap);

  ResponseUtil.out(ResultUtil.success(token));

  }

  }

  1.3 AuthenticationFailureHandler

  有登陆成功就有登录失败

  登录失败的时候调用这个方法,可以在其中做登录错误限制或者其他操作,我这里直接就是设置响应头的状态码为401,返回

  /**

  * 登录账号密码错误等情况下,会调用的处理类

  * @author Exrickx

  */

  @Slf4j

  @Component

  publicclassCustomerAuthenticationFailHandlerimplementsAuthenticationFailureHandler{

  @Override

  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {

  //设置response状态码,返回错误信息等

  ....

  ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.LOGIN_UNMATCH_ERROR));

  }

  }

  1.4 LogoutSuccessHandler

  登出注销的时候调用,Jwt有个缺点就是无法主动控制失效,可以采用Jwt+session的方式,比如删除存储在Redis的token

  这里需要注意,如果将SpringSecurity的session配置为无状态,或者不保存session,这里authentication为!!,注意空指针问题。(详情见下面的配置

  WebSecurityConfigurerAdapter)

  /**

  * 登出成功的调用类

  * @author zzzgd

  */

  @Component

  publicclassCustomerLogoutSuccessHandlerimplementsLogoutSuccessHandler{

  @Override

  public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

  ResponseUtil.out(ResultUtil.success("Logout Success!"));

  }

  }

  1.5 AccessDeniedHandler

  登录后,访问缺失权限的资源会调用。

  /**

  * 没有权限,被拒绝访问时的调用类

  * @author Exrickx

  */

  @Component

  @Slf4j

  publicclassCustomerRestAccessDeniedHandlerimplementsAccessDeniedHandler{

  @Override

  publicvoidhandle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {

  ResponseUtil.out(403, ResultUtil.failure(ErrorCodeConstants.PERMISSION_DENY));

  }

  }

  1.6 一个过滤器OncePerRequestFilter

  这里算是一个小重点。

  上面我们在登录成功后,返回了一个token,那怎么使用这个token呢?

  前端发起请求的时候将token放在请求头中,在过滤器中对请求头进行解析。

  如果有accessToken的请求头(可以自已定义名字),取出token,解析token,解析成功说明token正确,将解析出来的用户信息放到SpringSecurity的上下文中

  如果有accessToken的请求头,解析token失败(无效token,或者过期失效),取不到用户信息,放行

  没有accessToken的请求头,放行

  这里可能有人会疑惑,为什么token失效都要放行呢?

  这是因为SpringSecurity会自己去做登录的认证和权限的校验,靠的就是我们放在SpringSecurity上下文中的

  SecurityContextHolder.getContext.setAuthentication(authentication);,没有拿到authentication,放行了,SpringSecurity还是会走到认证和校验,这个时候就会发现没有登录没有权限。

  旧版本, 最新在底部

  /**

  * 过滤器,在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中

  * @author zzzgd

  */

  @Component

  @Slf4j

  publicclassCustomerJwtAuthenticationTokenFilterextendsOncePerRequestFilter{

  @Autowired

  CustomerUserDetailService customerUserDetailService;

  @Override

  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

  //请求头为 accessToken

  //请求体为 Bearer token

  String authHeader=request.getHeader(SecurityConstants.HEADER);

  if (authHeader !=&& authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)) {

  final String authToken=authHeader.substring(SecurityConstants.TOKEN_SPLIT.length);

  String username=JwtTokenUtil.parseTokenGetUsername(authToken);

  if (username !=&& SecurityContextHolder.getContext.getAuthentication==) {

  UserDetails userDetails=customerUserDetailService.loadUserByUsername(username);

  if (userDetails !=) {

  UsernamePasswordAuthenticationToken authentication=

  new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword, userDetails.getAuthorities);

  authentication.setDetails(new WebAuthenticationDetailsSource.buildDetails(request));

  SecurityContextHolder.getContext.setAuthentication(authentication);

  }

  }

  }

  chain.doFilter(request, response);

  }

  }

  1.7 实现UserDetails扩充字段

  这个接口表示的用户信息,SpringSecurity默认实现了一个User,不过字段寥寥无几,只有username,password这些,而且后面获取用户信息的时候也是获取的UserDetail。学习资料:Java进阶视频资源

  于是我们将自己的数据库的User作为拓展,自己实现这个接口。继承的是数据库对应的User,而不是SpringSecurity的User

  /**

  * CustomerUserDetails

  *

  * @author zgd

  * @date 2021/7/17 15:29

  */

  publicclassCustomerUserDetailsextendsUserimplementsUserDetails{

  private Collection authorities;

  publicCustomerUserDetails(User user){

  this.setId(user.getId);

  this.setUsername(user.getUsername);

  this.setPassword(user.getPassword);

  this.setRoles(user.getRoles);

  this.setStatus(user.getStatus);

  }

  publicvoidsetAuthorities(Collection authorities) {

  this.authorities=authorities;

  }

  /**

  * 添加用户拥有的权限和角色

  * @return

  */

  @Override

  public Collection getAuthorities {

  return this.authorities;

  }

  /**

  * 账户是否过期

  * @return

  */

  @Override

  publicbooleanisAccountNonExpired {

  return true;

  }

  /**

  * 是否禁用

  * @return

  */

  @Override

  publicbooleanisAccountNonLocked {

  return true;

  }

  /**

  * 密码是否过期

  * @return

  */

  @Override

  publicbooleanisCredentialsNonExpired {

  return true;

  }

  /**

  * 是否启用

  * @return

  */

  @Override

  publicbooleanisEnabled {

  return UserConstants.USER_STATUS_NORMAL.equals(this.getStatus);

  }

  }

  1.8 实现UserDetailsService

  SpringSecurity在登录的时候,回去数据库(或其他来源),根据username获取正确的user信息,就会根据这个service类,拿到用户的信息和权限。我们自己实现

  /**

  * @author zgd

  * @date 2021/1/16 16:27

  * @description 自己实现UserDetailService,用与SpringSecurity获取用户信息

  */

  @Service

  @Slf4j

  publicclassCustomerUserDetailServiceimplementsUserDetailsService{

  @Autowired

  private IUserService userService;

  /**

  * 获取用户信息,然后交给spring去校验权限

  * @param username

  * @return

  * @throws UsernameNotFoundException

  */

  @Override

  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

  //获取用户信息

  User user=userService.getUserRoleByUserName(username);

  if(user==){

  throw new UsernameNotFoundException("用户名不存在");

  }

  CustomerUserDetails customerUserDetails=new CustomerUserDetails(user);

  List authorities=new ArrayList<>;

  //用于添加用户的权限。只要把用户权限添加到authorities 就万事大吉。

  if (CollectionUtils.isNotEmpty(user.getRoles)){

  user.getRoles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_"+r.getRoleName)));

  }

  customerUserDetails.setAuthorities(authorities);

  log("authorities:{}", JSON.toJSONString(authorities));

  //这里返回的是我们自己定义的UserDetail

  return customerUserDetails;

  }

  }

  二、配置WebSecurityConfigurerAdapter

  我们需要将上面定义的handler和filter,注册到SpringSecurity。同时配置一些放行的url

  这里有一点需要注意:如果配置了下面的

  SessionCreationPolicy.STATELESS,则SpringSecurity不会保存session会话,在/logout登出的时候会拿不到用户实体对象。

  http.sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS);

  如果登出注销不依赖SpringSecurity,并且session交给redis的token来管理的话,可以按上面的配置。

  /**

  * @Author: zgd

  * @Date: 2021/1/15 17:42

  * @Description:

  */

  @Configuration

  @EnableWebSecurity

  @EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)// 控制@Secured权限注解

  publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{

  /**

  * 这里需要交给spring注入,而不是直接new

  */

  @Autowired

  private PasswordEncoder passwordEncoder;

  @Autowired

  private CustomerUserDetailService customerUserDetailService;

  @Autowired

  private CustomerAuthenticationFailHandler customerAuthenticationFailHandler;

  @Autowired

  private CustomerAuthenticationSuccessHandler customerAuthenticationSuccessHandler;

  @Autowired

  private CustomerJwtAuthenticationTokenFilter customerJwtAuthenticationTokenFilter;

  @Autowired

  private CustomerRestAccessDeniedHandler customerRestAccessDeniedHandler;

  @Autowired

  private CustomerLogoutSuccessHandler customerLogoutSuccessHandler;

  @Autowired

  private CustomerAuthenticationEntryPoint customerAuthenticationEntryPoint;

  /**

  * 该方法定义认证用户信息获取的来源、密码校验的规则

  *

  * @param auth

  * @throws Exception

  */

  @Override

  protected void configure(AuthenticationManagerBuilder auth) throws Exception {

  //auth.authenticationProvider(myauthenticationProvider) 自定义密码校验的规则

  //如果需要改变二手手游认证的用户信息来源,我们可以实现UserDetailsService

  auth.userDetailsService(customerUserDetailService).passwordEncoder(passwordEncoder);

  }

  @Override

  protected void configure(HttpSecurity http) throws Exception {

  /**

  * antMatchers: ant的通配符规则

  * ? 匹配任何单字符

   匹配0或者任意数量的字符,不包含"/"

   * 匹配0或者更多的目录,包含"/"

  */

  http

  .headers

  .frameOptions.disable;

  http

  //登录后,访问没有权限处理类

  .exceptionHandling.accessDeniedHandler(customerRestAccessDeniedHandler)

  //匿名访问,没有权限的处理类

  .authenticationEntryPoint(customerAuthenticationEntryPoint)

  ;

  //使用jwt的Authentication,来解析过来的请求是否有token

  http

  .addFilterBefore(customerJwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

  http

  .authorizeRequests

  //这里表示"/any"和"/ignore"不需要权限校验

  .antMatchers("/ignore/", "/login", "//register/**").permitAll

  .anyRequest.authenticated

  // 这里表示任何请求都需要校验认证(上面配置的放行)

  .and

  //配置登录,检测到用户未登录时跳转的url地址,登录放行

  .formLogin

  //需要跟前端表单的action地址一致

  .loginProcessingUrl("/login")

  .successHandler(customerAuthenticationSuccessHandler)

  .failureHandler(customerAuthenticationFailHandler)

  .permitAll

  //配置取消session管理,又Jwt来获取用户状态,否则即使token无效,也会有session信息,依旧判断用户为登录状态

  .and

  .sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

  //配置登出,登出放行

  .and

  .logout

  .logoutSuccessHandler(customerLogoutSuccessHandler)

  .permitAll

  .and

  .csrf.disable

  ;

  }

  }

  三、其他

  大概到这就差不多了,启动,localhost:8080/login,使用postman,采用form-data,post提交,参数是username和password,调用,返回token。

  将token放在header中,请求接口。学习资料:Java进阶视频资源

  3.1 不足之处

  上面是最简单的处理,还有很多优化的地方。比如

  控制token销毁?

  使用redis+token组合,不仅解析token,还判断redis是否有这个token。注销和主动失效token:删除redis的key

  控制token过期时间?如果用户在token过期前1秒还在操作,下1秒就需要重新登录,肯定不好

  1、考虑加入refreshToken,过期时间比token长,前端在拿到token的同时获取过期时间,在过期前一分钟用refreshToken调用refresh接口,重新获取新的token。

  2、 将返回的jwtToken设置短一点的过期时间,redis再存这个token,过期时间设置长一点。如果请求过来token过期,查询redis,如果redis还存在,返回新的token。(为什么redis的过期时间大于token的?因为redis的过期是可控的,手动可删除,以redis的为准)

  每次请求都会被OncePerRequestFilter 拦截,每次都会被UserDetailService中的获取用户数据请求数据库

  可以考虑做缓存,还是用redis或者直接保存内存中

  3.2 解决

  这是针对上面的2.2说的,也就是redis时间久一点,jwt过期后如果redis没过期,颁发新的jwt。

  不过更推荐的是前端判断过期时间,在过期之前调用refresh接口拿到新的jwt。

  为什么这样?

  如果redis过期时间是一周,jwt是一个小时,那么一个小时后,拿着这个过期的jwt去调,就可以想创建多少个新的jwt就创建,只要没过redis的过期时间。当然这是在没对过期的jwt做限制的情况下,如果要考虑做限制,比如对redis的value加一个字段,保存当前jwt,刷新后就用新的jwt覆盖,refresh接口判断当前的过期jwt是不是和redis这个一样。

  总之还需要判断刷新token的时候,过期jwt是否合法的问题。总不能去年的过期token也拿来刷新吧。

  而在过期前去刷新token的话,至少不会发生这种事情

  不过我这里自己写demo,采用的还是2.2的方式,也就是过期后给个新的,思路如下:

  登录后颁发token,token有个时间戳,同时以username拼装作为key,保存这个时间戳到缓存(redis,cache)

  请求来了,过滤器解析token,没过期的话,还需要比较缓存中的时间戳和token的时间戳是不是一样 ,如果时间戳不一样,说明该token不能刷新。无视

  注销,清除缓存数据

  这样就可以避免token过期后,我还能拿到这个token无限制的refresh。

  不过这个还是有细节方面问题,并发下同时刷新token这些并没有考虑,部分代码如下

  旧版本, 最新在底部

  /**

  * 过滤器,在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中

  * @author zzzgd

  */

  @Component

  @Slf4j

  publicclassCustomerJwtAuthenticationTokenFilterextendsOncePerRequestFilter{

  @Autowired

  CustomerUserDetailService customerUserDetailService;

  @Autowired

  UserSessionService userSessionService;

  @Autowired

  UserTokenManager userTokenManager;

  @Override

  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

  //请求头为 accessToken

  //请求体为 Bearer token

  String authHeader=request.getHeader(SecurityConstants.HEADER);

  if (authHeader !=&& authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)) {

  final String authToken=authHeader.substring(SecurityConstants.TOKEN_SPLIT.length);

  String username;

  Claims claims;

  try {

  claims=JwtTokenUtil.parseToken(authToken);

  username=claims.getSubject;

  } catch (ExpiredJwtException e) {

  //token过期

  claims=e.getClaims;

  username=claims.getSubject;

  CustomerUserDetails userDetails=userSessionService.getSessionByUsername(username);

  if (userDetails !=){

  //session未过期,比对时间戳是否一致,是则重新颁发token

  if (isSameTimestampToken(username,e.getClaims)){

  userTokenManager.awardAccessToken(userDetails,true);

  }

  }

  }

  //避免每次请求都请求数据库查询用户信息,从缓存中查询

  CustomerUserDetails userDetails=userSessionService.getSessionByUsername(username);

  if (username !=&& SecurityContextHolder.getContext.getAuthentication==) {

  // UserDetails userDetails=customerUserDetailService.loadUserByUsername(username);

  if (userDetails !=) {

  if(isSameTimestampToken(username,claims)){

  //必须token解析的时间戳和session保存的一致

  UsernamePasswordAuthenticationToken authentication=

  new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword, userDetails.getAuthorities);

  authentication.setDetails(new WebAuthenticationDetailsSource.buildDetails(request));

  SecurityContextHolder.getContext.setAuthentication(authentication);

  }

  }

  }

  }

  chain.doFilter(request, response);

  }

  /**

  * 判断是否同一个时间戳

  * @param username

  * @param claims

  * @return

  */

  privatebooleanisSameTimestampToken(String username, Claims claims){

  Long timestamp=userSessionService.getTokenTimestamp(username);

  Long jwtTimestamp=(Long) claims.get(SecurityConstants.TIME_STAMP);

  return timestamp.equals(jwtTimestamp);

  }

  }

  /**

  * UserTokenManager

  * token管理

  *

  * @author zgd

  * @date 2021/7/19 15:25

  */

  @Component

  publicclassUserTokenManager{

  @Autowired

  private UserAuthProperties userAuthProperties;

  @Autowired

  private UserSessionService userSessionService;

  /**

  * 颁发token

  * @param principal

  * @author zgd

  * @date 2021/7/19 15:34

  * @return void

  */

  publicvoidawardAccessToken(CustomerUserDetails principal,boolean isRefresh) {

  //颁发token 确定时间戳,保存在session中和token中

  long mill=System.currentTimeMillis;

  userSessionService.saveSession(principal);

  userSessionService.saveTokenTimestamp(principal.getUsername,mill);

  Map param=new HashMap<>(4);

  param.put(UserConstants.USER_ID,principal.getId);

  param.put(SecurityConstants.TIME_STAMP,mill);

  String token=JwtTokenUtil.generateToken(principal.getUsername, param,userAuthProperties.getJwtExpirationTime);

  HashMap map=Maps.newHashMapWithExpectedSize(1);

  map.put(SecurityConstants.HEADER,token);

  int code=isRefresh ? 201 : 200;

  ResponseUtil.outWithHeader(code,ResultUtil.success,map);

  }

  }

  针对token解析的过滤器做了优化:

  如果redis的session没过期, 但是请求头的token过期了, 判断时间戳一致后, 颁发新token并返回

  如果redis的session没过期, 但是请求头的token过期了, 时间戳不一致, 说明当前请求的token无法刷新token, 设置响应码为401返回

  如果请求头的token过期了, 但是redis的session失效或未找到, 直接放行, 交给后面的权限校验处理(也就是没有给上下文SecurityContextHolder设置登录信息, 后面如果判断这个请求缺少权限会自行处理)

  /**

  * 过滤器,在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中

  * @author zzzgd

  */

  @Component

  @Slf4j

  public classCustomerJwtAuthenticationTokenFilterextendsOncePerRequestFilter{

  @Autowired

  CustomerUserDetailService customerUserDetailService;

  @Autowired

  UserSessionService userSessionService;

  @Autowired

  UserTokenManager userTokenManager;

  @Override

  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

  //请求头为 accessToken

  //请求体为 Bearer token

  String authHeader=request.getHeader(SecurityConstants.HEADER);

  if (authHeader !=&& authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)) {

  //请求头有token

  final String authToken=authHeader.substring(SecurityConstants.TOKEN_SPLIT.length);

  String username;

  Claims claims;

  try {

  claims=JwtTokenUtil.parseToken(authToken);

  username=claims.getSubject;

  } catch (ExpiredJwtException e) {

  //token过期

  claims=e.getClaims;

  username=claims.getSubject;

  CustomerUserDetails userDetails=userSessionService.getSessionByUsername(username);

  if (userDetails !=){

  //session未过期,比对时间戳是否一致,是则重新颁发token

  if (isSameTimestampToken(username,e.getClaims)){

  userTokenManager.awardAccessToken(userDetails,true);

  //直接设置响应码为201,直接返回

  return;

  }else{

  //时间戳不一致.无效token,无法刷新token,响应码401,前端跳转登录页

  ResponseUtil.out(HttpStatus.UNAUTHORIZED.value,ResultUtil.failure(ErrorCodeConstants.REQUIRED_LOGIN_ERROR));

  return;

  }

  }else{

  //直接放行,交给后面的handler处理,如果当前请求是需要访问权限,则会由

  CustomerRestAccessDeniedHandler处理

  chain.doFilter(request, response);

  return;

  }

  }

  //避免每次请求都请求数据库查询用户信息,从缓存中查询

  CustomerUserDetails userDetails=userSessionService.getSessionByUsername(username);

  if (username !=&& SecurityContextHolder.getContext.getAuthentication==) {

  // UserDetails userDetails=customerUserDetailService.loadUserByUsername(username);

  if (userDetails !=) {

  if(isSameTimestampToken(username,claims)){

  //必须token解析的时间戳和session保存的一致

  UsernamePasswordAuthenticationToken authentication=

  new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword, userDetails.getAuthorities);

  authentication.setDetails(new WebAuthenticationDetailsSource.buildDetails(request));

  SecurityContextHolder.getContext.setAuthentication(authentication);

  }

  }

  }

  }

  chain.doFilter(request, response);

  }

  /**

  * 判断是否同一个时间戳

  * @param username

  * @param claims

  * @return

  */

  privatebooleanisSameTimestampToken(String username, Claims claims){

  Long timestamp=userSessionService.getTokenTimestamp(username);

  Long jwtTimestamp=(Long) claims.get(SecurityConstants.TIME_STAMP);

  return timestamp.equals(jwtTimestamp);

  }

  }

  来源:blog.csdn/zzzgd_666/article/details/96444829

  #投 稿 通 道#

  让你的博客被更多人看到

  如果你在 CSDN、博客园、掘金等平台有写技术博客的习惯,想让自己的原创博客被更多人看到,可以来 Java后端 投稿。

  Java后端 鼓励读者投稿个人技术博客、面试经验、教程。不管是入门的图文教程、还是热门技术讲解,只要你喜欢写东西,我们欢迎你来投稿。

   稿件基本要求:

  ? 文章确系个人原创作品,如果在其他非公众号渠道有过发表也可以,只要是个人原创即可。

  ? 稿件建议以 markdown 格式撰写,文中配图以附件形式发送,要求图片清晰、语句通顺。

  ? 如果被采纳的原创稿件,我们将提供稿费以及个人影响力曝光,具体依据文章阅读量和质量结算稿费。

   投稿通道:

  ? 投稿请联系下方微信,备注:原创投稿

  △长按添加 Java后端 小编

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
20天前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
71 5
|
2月前
|
JSON 安全 算法
|
25天前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
56 8
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
45 0
|
设计模式 前端开发 Java
基于Springboot实现专业认证材料管理系统
该知识产权服务平台系统项目采用mvc设计模式, 其中知识产权服务平台系统的视图与知识产权服务平台系统业务逻辑进行了分层设计, 特别方便后续知识产权服务平台系统系统的开发 设计这种mvc的架构的好处是完全的可以将业务进行分层, 进行高内聚低耦合, 分为service层, dao层, controller层, 架构清晰 本项目主要基于Springboot 和ruoyi来开发一套专业认证材料管理系统,对各专业相关的文档材料进行管理,主要包含的功能模块有: 系统管理:用户管理、角色管理、菜单管理、操作日志 业务模块:专业管理、认证材料管理、相关网站管理
187 0
基于Springboot实现专业认证材料管理系统
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
178 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
115 62
|
7天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
54 13
|
15天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
80 2
下一篇
DataWorks