项目使用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>