一、首先导入依赖
<!--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了,因为这个是之前的了,我们要把最新的复制过去:
换了后,就成功了: