一、首先导入依赖
<!--jwt依赖 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
二、编写工具类
package com.zimug.jwtserver.config.auth.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; @Data @ConfigurationProperties(prefix = "jwt") @Component public class JwtTokenUtil { private String secret; private Long expiration; private String header; /** * 生成token令牌 * * @param userDetails 用户 * @return 令token牌 */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(2); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return generateToken(claims); } /** * 从令牌中获取用户名 * * @param token 令牌 * @return 用户名 */ public 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 Boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { return false; } } /** * 刷新令牌 * * @param token 原令牌 * @return 新令牌 */ public String refreshToken(String token) { String refreshedToken; try { Claims claims = getClaimsFromToken(token); claims.put("created", new Date()); refreshedToken = generateToken(claims); } catch (Exception e) { refreshedToken = null; } return refreshedToken; } /** * 验证令牌 * * @param token 令牌 * @param userDetails 用户 * @return 是否有效 */ public Boolean validateToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } /** * 从claims生成令牌,如果看不懂就看谁调用它 * * @param claims 数据声明 * @return 令牌 */ private 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 Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } }
三、在配置文件里配置属性
jwt: secret: asfdsafsdasadf expriation: 3600000 header: JWTHeaderName
四、编写服务类和控制器
package com.example.jwt.config.auth.jwt; import com.example.jwt.config.exception.CustomException; import com.example.jwt.config.exception.CustomExceptionType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class JwtAuthService { @Resource AuthenticationManager authenticationManager; @Resource UserDetailsService userDetailsService; @Resource JwtTokenUtil jwtTokenUtil; /** * 登录认证换取JWT令牌 * @return JWT */ public String login(String username,String password) throws CustomException { try { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); }catch (AuthenticationException e){ throw new CustomException(CustomExceptionType.USER_INPUT_ERROR ,"用户名或者密码不正确"); } UserDetails userDetails = userDetailsService.loadUserByUsername(username); return jwtTokenUtil.generateToken(userDetails); } public String refreshToken(String oldToken){ if(!jwtTokenUtil.isTokenExpired(oldToken)){ return jwtTokenUtil.refreshToken(oldToken); } return null; } }
package com.example.jwt.config.auth.jwt; import com.example.jwt.config.exception.AjaxResponse; import com.example.jwt.config.exception.CustomException; import com.example.jwt.config.exception.CustomExceptionType; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Map; @RestController public class JwtAuthController { @Resource JwtAuthService jwtAuthService; @RequestMapping(value = "/authentication") public AjaxResponse login(@RequestBody Map<String,String> map){ String username = map.get("username"); String password = map.get("password"); if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){ return AjaxResponse.error( new CustomException(CustomExceptionType.USER_INPUT_ERROR, "用户名或者密码不能为空")); } try { return AjaxResponse.success(jwtAuthService.login(username, password)); }catch (CustomException e){ return AjaxResponse.error(e); } } @RequestMapping(value = "/refreshtoken") public AjaxResponse refresh(@RequestHeader("${jwt.header}") String token){ return AjaxResponse.success(jwtAuthService.refreshToken(token)); } }
放行一下接口这两个接口:
.antMatchers("/authentication","/refreshtoken").permitAll()
注意要自己要在SecurityConfig里声明一下manager,service那边才能用
@Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
到这里就能生成jwt令牌和刷新令牌了。
五、编写过滤器,进行鉴权
package com.example.jwt.config.auth.jwt; import com.example.jwt.config.auth.MyUserDetailsService; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.annotation.Resource; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource JwtTokenUtil jwtTokenUtil; @Resource MyUserDetailsService myUserDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //请求头里需要有token String jwtToken = request.getHeader(jwtTokenUtil.getHeader()); if(!StringUtils.isEmpty(jwtToken)){ //解密取出用户名 String username = jwtTokenUtil.getUsernameFromToken(jwtToken); if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){ UserDetails userDetails = myUserDetailsService.loadUserByUsername(username); if(jwtTokenUtil.validateToken(jwtToken,userDetails)){ //给使用该JWT令牌的用户进行授权 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } } filterChain.doFilter(request,response); } }
添加过滤器在用户名密码过滤器之前:
.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class)
六、解决跨域访问的问题
开启:
.cors()
@Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080")); configuration.setAllowedMethods(Arrays.asList("GET","POST")); configuration.applyPermitDefaultValues(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
七、防止跨域攻击
//仿跨站攻击,在登陆后会返回XSRF-TOKEN的cookie,在除了get请求的其他类型时均需带上 X-XSRF-TOKEN 的header,每请求一次该cookie就会刷新,下次带上新的值才能访问 http.csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())//允许脚本读取cookie .ignoringAntMatchers("/authentication")
然后写个控制器测试下:
@RequestMapping(value = "/hello") public String hello() { return "world"; }
可以看到get请求时不需要X-XSRF-TOKEN请求头
换成post请求,勾上xsrf请求头,还是403了,因为这个是之前的了,我们要把最新的复制过去:
换了后,就成功了: