Bootstrap

SpringSecurity整合JWT权限验证实现前后端分离,配合使用 Redis实现token超时的刷新机制

项目使用SpringSecurity整合JWT实现权限验登陆,下面简单描述下整个流程。

1.登陆成功后生成JWT token 返回给前端,前端再次访问时携带这个jwt token,服务端收到后解析这个token,判断这个token是否超过最大有效期,如没有超过最大有效期但这个token过期了,就返回刷新后的jwt给前端,但超过了最大有效期就要用户重新登陆了,下面是具体的代码实现,有 不足之处多多指导哦。

1.配置验证过期以及刷新JWT的过滤器

@Component
public class JwtCheckTokenFilter extends OncePerRequestFilter {

//	@Autowired
//	UserDetailsService userservice;
	
	@Autowired
	UserService userService;

	@Autowired
	RedisUtil redisUtil;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String token = request.getHeader("jwt_token");
		if (StringUtils.isEmpty(token))
			token = CookieUtil.getcookie(request, "jwt_token");
		if (!StringUtils.isEmpty(token)) {
			String username = JwtTokenUtil.getUsernameFromToken(token);
			if (username != null) {
				if (SecurityContextHolder.getContext().getAuthentication() == null) {
					//UserDetails userDetails = userservice.loadUserByUsername(username);
					UserMsg userMsg = userService.findByUsername(username);
					UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
					userMsg,token,getAuthorities(userMsg));
					authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
					SecurityContextHolder.getContext().setAuthentication(authentication);
				}
			} else {
				String user_key = redisUtil.getKeyByToken(token,"token");
				if (user_key != null) {
					UserMsg userMsg = userService.findByUsername(user_key);
					//UserDetails userDetails = userservice.loadUserByUsername(username2);
                     //返回刷新后的JWT
					String new_token = JwtTokenUtil.generateToken(userMsg);
                    //刷新redis中的JWT
					redisUtil.set("token"+redis_username,new_token,60*60*24*10);
					CookieUtil.addcookie(ImmutableMap.of("jwt_token", new_token), request, response);
				} else {
					SecurityContextHolder.clearContext();
				}
			}
		}else SecurityContextHolder.clearContext();
		filterChain.doFilter(request, response);
	}
	
	private Collection<GrantedAuthority> getAuthorities(UserMsg userMsg) {
		Stream<String> roles = 
				Objects.equals(1,userMsg.getIsadmin())?
				Stream.of("ROLE_admin","select","update","insert","delete")
				:Stream.of("ROLE_user","select","update","insert");
		return roles.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
	}
}

 2.SpringSecurity的配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
	
	@Autowired
	private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
	@Autowired
	private MyAuthenticationFailHandler myAuthenticationFailHandler;
	@Autowired
	private MyAccessFailHandler myAccessFailHandler;
//	@Autowired
//	private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
	@Autowired
	private JwtCheckTokenFilter jwtChecktokenfilter;
	@Autowired
	private UserDetailsService userDetailsService;
	@Autowired
	private DataSource dataSource;
	@Autowired
	private MyAuthenticationProvider myAuthenticationProvider;

	
	@Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
	
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     // auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
		auth.authenticationProvider(myAuthenticationProvider);
		
    }
	
	@Bean
	public PersistentTokenRepository tokenRepository(){
		JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
		tokenRepositoryImpl.setDataSource(dataSource);
		//tokenRepositoryImpl.setCreateTableOnStartup(true);
		return tokenRepositoryImpl;
	}

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	http
    	.exceptionHandling()
    	//.authenticationEntryPoint(myAuthenticationEntryPoint)
    	.accessDeniedHandler(myAccessFailHandler)
    	.and()
    	.addFilterBefore(jwtChecktokenfilter,UsernamePasswordAuthenticationFilter.class);
    	http
    	.rememberMe()
    	.rememberMeParameter("remember")
    	.tokenRepository(tokenRepository())
    	.userDetailsService(userDetailsService)
    	.tokenValiditySeconds(60*60*24)
    	.and()
    	.formLogin()
    	.loginPage("/page/login_page")
    	.loginProcessingUrl("/login")
    	.usernameParameter("user")
    	.passwordParameter("pass")
    	.successHandler(myAuthenticationSuccessHandler)
		.failureHandler(myAuthenticationFailHandler)
		.and()
		.authorizeRequests()
    	.antMatchers(
    			"/swagger-ui.html#/**",
    			"/login/**",
    			"/page/**",
    			"/user/reg",
    			"/logout/**",
    			"/oauth/**",
    			"/file/**",
    			"/html/**",
    			"/image/**",
    			"/js/**"
    			)
    	.permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .csrf()
        .disable();
    	http.logout().deleteCookies("jwt_token");
    	http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

3.自定义登陆验证逻辑类

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
	@Autowired
	private UserMapper userMapper;
    //@ConditionalOnMissingBean(PasswordEncoder.class)
	@Bean
	@Primary
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
	
	@Override
	public Authentication authenticate(Authentication authentication){
		String username = authentication.getName();
		String password = (String) authentication.getCredentials();
		//System.out.println(username + " : " + password);
		if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
			throw new UsernameNotFoundException("用户名密码不能为空");
		UserMsg user = userMapper.findByUsername(username);
		if (user==null)throw new UsernameNotFoundException("用户名错误");
		//System.out.println("user.getPassword() "+user.getPassword());
		if (!passwordEncoder().matches(password+user.getSalt(),user.getPassword()))
			throw new BadCredentialsException("密码错误");
		//UserDetails jwtUser = new JwtUser(username, password,getAuthorities(String.format("ROLE_%s",user.getRole())));
		return new UsernamePasswordAuthenticationToken(user,password,getAuthorities(user));
	}

	@Override
	public boolean supports(Class<?> aClass) {
		return UsernamePasswordAuthenticationToken.class.equals(aClass);
	}

	private Collection<GrantedAuthority> getAuthorities(UserMsg userMsg) {
		Stream<String> roles = 
				Objects.equals(1,userMsg.getIsadmin())?
				Stream.of("ROLE_admin","select","update","insert","delete")
				:Stream.of("ROLE_user","select","update","insert");
		return roles.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
	}
}

4.自定义登陆成功类

@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	RedisUtil redisUtil;
	
	@Autowired
	UserService userService;
	
	@Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
		logger.info("登陆成功.....................................");
		//UserDetails userDetails = (UserDetails)authentication.getPrincipal();
		//UserMsg userMsg = userService.findByUsername(userDetails.getUsername());
		UserMsg userDetails = (UserMsg)authentication.getPrincipal();
		String jwt_token = JwtTokenUtil.generateToken(userDetails);
		Map<String,String> maps = new HashMap<String,String>();
		maps.put("jwt_token",jwt_token);
        //将JWT存入redis设置过期时间10天,超过10天让用户重新登陆
		redisUtil.set("jwt_token"+userDetails.getUsername(),jwt_token,60*60*24*10);
		CookieUtil.addcookie(maps,request,response);
        RequestCache requestCache = new HttpSessionRequestCache();
        SavedRequest savedRequest = requestCache.getRequest(request,response);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSONObject.toJSONString(ResultUtil.resultOK(savedRequest!=null?savedRequest.getRedirectUrl():"/page/index")));
    }
}

5.自定义登陆失败类

@Component
public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
	private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    	exception.printStackTrace();
        logger.info("登录失败");
        response.setContentType("application/json;charset=UTF-8");
        JSONObject json = new JSONObject();
        json.put("codes",500);
        json.put("messages",exception.getMessage());
        response.getWriter().write(json.toJSONString());
    }
}

6.JWT工具类

public abstract class JwtTokenUtil {
	private static final String secret = UUID.randomUUID().toString().replace("-","");
    private static final Long expiration = 60*10*1000L;//令牌过期时间
    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private static String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder()
        		.setClaims(claims)
        		.setExpiration(expirationDate)
        		.signWith(SignatureAlgorithm.HS512, secret)
        		.compact();
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 生成令牌
     *
     * @param userDetails 用户
     * @return 令牌
     */
    public static String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put(Claims.SUBJECT, userDetails.getUsername());
        claims.put(Claims.ISSUED_AT, new Date());
        return generateToken(claims);
    }
    
    public static String generateToken(UserMsg userMsg) {
        Map<String,Object> claims = new HashMap<>();
        claims.put("username",userMsg.getUsername());
        claims.put("uid",userMsg.getUid());
        claims.put("header",userMsg.getHeader());
        claims.put("lasttime",LocalDateTime.now());
        return generateToken(claims);
    }
    
    public static <T> T getUserByToken(String token,Class<T> classs) {
    	T user = null;
    	try {
    		Claims claims = getClaimsFromToken(token);
    		if(claims==null)return null;
    		user = classs.newInstance();
    		 for(Field field : classs.getDeclaredFields()){
    			String name = field.getName();
    			if(!claims.containsKey(name))continue;
    			field.setAccessible(true);
    			field.set(user,claims.get(name));
    		 }
		} catch (Exception e) {
			e.printStackTrace();
		}
        return user;
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public static String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    
    

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public static String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put(Claims.ISSUED_AT, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     *
     * @param token       令牌
     * @param userDetails 用户
     * @return 是否有效
     */
    public static Boolean validateToken(String token, UserDetails userDetails) {
        JwtUser user = (JwtUser) userDetails;
        String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }

7.用到的mavem依赖包

        <!-- redis -->
		<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.0</version>
		</dependency>
        <dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
		</dependency>

;