JWT前后端分离,交互流程
1.客户端通过用户名、密码请求服务器端登录
2.服务器验证用户名、密码通过后、生成token返回到客户端
3.客户端拿到token后存储到客户端本地,h5可存储到本地localstorage中
4.客户端所有请求需要登录后、才允许发送的请求、需要在header头添加token
5.服务器从header头拿到token解析、解析成功后执行业务逻辑、解析失败返回状态403、客户端获取403状态后需跳转登录页面重新登录
集成步骤
1.pom.xml添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
2.增加相关Java配置
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private TokenUtil tokenUtil;
@Override
public void configure(WebSecurity web) throws Exception {
// Filters will not get executed for the resources
web.ignoring().antMatchers("/", "/resources/**", "/static/**", "/public/**", "/webui/**", "/h2-console/**"
, "/configuration/**", "/swagger-ui/**", "/swagger-resources/**", "/api-docs", "/api-docs/**", "/v2/api-docs/**"
, "/*.html", "/**/*.html" ,"/**/*.css","/**/*.js","/**/*.png","/**/*.jpg", "/**/*.gif", "/**/*.svg", "/**/*.ico", "/**/*.ttf","/**/*.woff","/**/*.otf");
}
//If Security is not working check application.properties if it is set to ignore
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().and()
.anonymous().and()
// Disable Cross site references
.csrf().disable()
// Add CORS Filter
.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
// Custom Token based authentication based on the header previously given to the client
.addFilterBefore(new VerifyTokenFilter(tokenUtil), UsernamePasswordAuthenticationFilter.class)
// custom JSON based authentication by POST of {"username":"<name>","password":"<password>"} which sets the token header upon authentication
.authorizeRequests()
.anyRequest().authenticated()
;
}
}
@Service
@Slf4j
public class TokenUtil {
//private static final long VALIDITY_TIME_MS = 10 * 24 * 60 * 60 * 1000;// 10 days Validity
// 2 hours validity
private static final long VALIDITY_TIME_MS = 2 * 60 * 60 * 1000;
private static final String AUTH_HEADER_NAME = "Authorization";
public static final String REDIS_TOKEN_KEY = "redis_token_key!@#@#@%%^&";
private String secret="mrin";
@Autowired
RedisUtil redisUtil;
public Optional<Authentication> verifyToken(HttpServletRequest request) {
final String token = request.getHeader(AUTH_HEADER_NAME);
if (token != null && !token.isEmpty()){
final TokenUser user = parseUserFromToken(token.replace("Bearer","").trim());
if (user != null) {
String redisToken = this.getRedisToken(user.getUser().getUserId());
if(StringUtils.isNotEmpty(redisToken)){
return Optional.of(new UserAuthentication(user));
}
}
}
return Optional.empty();
}
//Get User Info from the Token
public TokenUser parseUserFromToken(String token){
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
User user = new User();
user.setUserId( (String)claims.get("userId"));
user.setRole(Role.valueOf((String)claims.get("role")));
if (user.getUserId() != null && user.getRole() != null) {
if(user.getPassword() == null){
user.setPassword(user.getUserId());
}
return new TokenUser(user);
} else {
return null;
}
}
public String createTokenForUser(TokenUser tokenUser) {
return createTokenForUser(tokenUser.getUser());
}
public String createTokenForUser(User user) {
return Jwts.builder()
// .setExpiration(new Date(System.currentTimeMillis() + VALIDITY_TIME_MS))
.setSubject(user.getUserId())
.claim("createTime", System.currentTimeMillis())
.claim("userId", user.getUserId())
.claim("role", user.getRole().toString())
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public void putRedisToken(String key,String token){
try {
log.info("put token for key:"+key);
redisUtil.set(key,token);
} catch (Exception e) {
e.printStackTrace();
log.info("put token error:"+ e.getMessage());
}
}
public String getRedisToken(String key){
try {
log.info("get token for key:"+key);
return redisUtil.get(key);
} catch (Exception e) {
e.printStackTrace();
log.info("get token error:"+ e.getMessage());
}
return null;
}
public void removeRedisToken(String key){
try {
log.info("remove token for key:"+key);
redisUtil.delete(key);
} catch (Exception e) {
e.printStackTrace();
log.info("remove token error:"+ e.getMessage());
}
}
}
public class TokenUser extends org.springframework.security.core.userdetails.User {
private User user;
//For returning a normal user
public TokenUser(User user) {
super( user.getUserId(), user.getPassword(), AuthorityUtils.createAuthorityList(user.getRole().toString() ) );
//super(user.getUserName(), user.getPassword(), true, true, true, true, AuthorityUtils.createAuthorityList(user.getRole().toString()));
this.user = user;
}
public User getUser() {
return user;
}
public String getRole() {
return user.getRole().toString();
}
}
@Entity
public class User {
@Id
@Getter @Setter private String userId;
@Getter @Setter private String password = "";
@Getter @Setter private String company;
@Getter @Setter private String firstName;
@Getter @Setter private String lastName;
@Getter @Setter private String email;
@JsonIgnore @Getter @Setter private int securityProviderId;
@JsonIgnore @Getter @Setter private int defaultCustomerId;
@JsonIgnore @Getter @Setter private String phone;
@JsonIgnore @Getter @Setter private String address1;
@JsonIgnore @Getter @Setter private String address2;
@JsonIgnore @Getter @Setter private String country;
@JsonIgnore @Getter @Setter private String postal;
@Enumerated(EnumType.STRING)
@Getter @Setter private Role role;
//@JsonIgnore
@JsonIgnore @Getter @Setter private boolean isActive;
//@JsonIgnore
@JsonIgnore @Getter @Setter private boolean isBlocked;
@JsonIgnore @Getter @Setter private String secretQuestion;
@JsonIgnore @Getter @Setter private String secretAnswer;
@JsonIgnore @Getter @Setter private boolean enableBetaTesting;
@JsonIgnore @Getter @Setter private boolean enableRenewal;
}
public enum Role {
USER, ADMIN
}
3.实现自定义过滤器
/**
This filter checks if there is a token in the Request service header and the token is not expired
it is applied to all the routes which are protected
*/
@Slf4j
public class VerifyTokenFilter extends GenericFilterBean {
private final TokenUtil tokenUtil;
//private AuthenticationFailureHandler loginFailureHandler = new SimpleUrlAuthenticationFailureHandler();
public VerifyTokenFilter(TokenUtil tokenUtil) {
this.tokenUtil = tokenUtil;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
Optional<Authentication> authentication = tokenUtil.verifyToken(request);
if (authentication.isPresent()) {
SecurityContextHolder.getContext().setAuthentication(authentication.get());
}
else{
SecurityContextHolder.getContext().setAuthentication(null);
}
filterChain.doFilter(req, res);
}catch (JwtException e) {
log.error("token验证失败:"+e.getMessage());
responseErrorInfo(response,e.getMessage());
}catch (Exception e) {
log.error("token验证失败:"+e.getMessage());
responseErrorInfo(response,e.getMessage());
}finally {
SecurityContextHolder.getContext().setAuthentication(null);
}
}
private void responseErrorInfo(HttpServletResponse response,String message){
try {
response.setHeader( "Content-type" , MediaType.APPLICATION_JSON_UTF8_VALUE );
response.setCharacterEncoding( StandardCharsets.UTF_8.displayName() );
Result result = new Result();
result.setStatus(Result.Status.ERROR);
result.setMessage(message);
result.setCode(TokenCodeEnum.VALIDFAIED.getCode());
response.getWriter().print(JSONObject.toJSONString(result));
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.集成系统业务登录模块
//登录后生成token
User user = new User();
user.setUserId(userInfoVO.getLoginName() + userVO.getClientTypeEnum().getCode());
user.setPassword(userInfoVO.getLoginName());
TokenUser currentUser = new TokenUser(user);
String tokenString = this.tokenUtil.createTokenForUser(currentUser);
userInfoVO.setToken(tokenString);
this.tokenUtil.putRedisToken(userInfoVO.getLoginName()+userVO.getClientTypeEnum(),tokenString);