SpringSecurity之整合JWT

1. 写在前面的话

  • 首先, 本文依旧是笔者学习SpringSecurity遇到的坑的一些感悟, 因此, 不会去介绍一些基本概念, 如有需求, 请百度!

  • 其次, 本文有些做法可能存在问题, 希望大家不吝指教

  • 最后, 本文也是通过参考网上的博文再通过自己整合完成, 因此有相同的代码请谅解!

2. JWT依赖以及工具类的编写

本文是在前几篇的SpringSecurity项目上编写的, 因此, 本文只重点说一下新增的功能

本文使用的JWT是 JJWT, 当然还有其他的选择

<!--Jwt, 这里用的是JJWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
  • JWT 工具类

    package com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityConfigUtil;
    
    import com.wang.spring_security_framework.common.SecurityConstant;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.ExpiredJwtException;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component; import java.util.Collection;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map; //JWT工具类
    @Component
    public class JWTUtil {
    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_USERNAME = "sub"; //生成JWT
    public String JWTCreator(Authentication authResult) {
    //获取登录用户的角色
    Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
    StringBuffer stringBuffer = new StringBuffer();
    for (GrantedAuthority authority : authorities) {
    stringBuffer.append(authority.getAuthority()).append(",");
    }
    String username = authResult.getName();
    //自定义属性
    Map<String, Object> claims = new HashMap<>();
    //自定义属性, 放入用户拥有的权限
    claims.put(SecurityConstant.AUTHORITIES, stringBuffer);
    //自定义属性, 放入创建时间
    claims.put(CLAIM_KEY_CREATED, new Date());
    //自定义属性, 放入主题, 即用户名
    claims.put(CLAIM_KEY_USERNAME, username); return Jwts.builder()
    //自定义属性
    .setClaims(claims)
    //过期时间
    .setExpiration(new Date(System.currentTimeMillis() + SecurityConstant.EXPIRATION_TIME))
    //签名
    .signWith(SignatureAlgorithm.HS256, SecurityConstant.JWT_SIGN_KEY)
    .compact();
    } //生成Token的Claims, 调用下面的方法, 返回一个JWT
    public String generateToken(User userDetails) {
    Map<String, Object> claims = new HashMap<>();
    //获取用户名, 使用sub作为key和设置subject是一样的
    claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
    //获取登录用户的角色
    Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
    StringBuffer stringBuffer = new StringBuffer();
    for (GrantedAuthority authority : authorities) {
    stringBuffer.append(authority.getAuthority()).append(",");
    }
    claims.put(SecurityConstant.AUTHORITIES, stringBuffer);
    //获取创建时间
    claims.put(CLAIM_KEY_CREATED, new Date());
    return generateToken(claims);
    } //根据Claims生成JWT
    public String generateToken(Map<String, Object> claims) {
    return Jwts.builder()
    .setClaims(claims)
    .setExpiration(new Date(System.currentTimeMillis() + SecurityConstant.EXPIRATION_TIME))
    .signWith(SignatureAlgorithm.HS256, SecurityConstant.JWT_SIGN_KEY)
    .compact();
    } //解析JWT, 获得Claims
    private Claims getClaimsFromToken(String token) {
    Claims claims;
    try {
    claims = Jwts.parser()
    .setSigningKey(SecurityConstant.JWT_SIGN_KEY)
    .parseClaimsJws(token)
    .getBody();
    } catch (ExpiredJwtException e) {
    //如果过期,在异常中调用, 返回claims, 否则无法解析过期的token
    claims = e.getClaims();
    } catch (Exception e) {
    claims = null;
    }
    return claims;
    } //从JWT中获得用户名
    public String getUsernameFromToken(String token) {
    try {
    return getClaimsFromToken(token).getSubject();
    } catch (ExpiredJwtException e) {
    //如果过期, 需要在此处异常调用如下的方法, 否则拿不到用户名
    return e.getClaims().getSubject();
    } catch (Exception e) {
    e.printStackTrace();
    return null;
    }
    // catch (Exception e) {
    // username = null;
    // }
    } //从JWT中获取创建时间 ==> 在自定义区域内
    public Date getCreatedDateFromToken(String token) {
    Date created;
    try {
    Claims claims = getClaimsFromToken(token);
    created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
    } catch (Exception e) {
    created = null;
    }
    return created;
    } //从JWT中获取过期时间
    public Date getExpirationDateFromToken(String token) {
    Date expiration;
    try {
    Claims claims = getClaimsFromToken(token);
    expiration = claims.getExpiration();
    } catch (Exception e) {
    expiration = null;
    }
    return expiration;
    } //判断JWT是否过期
    private Boolean isTokenExpired(String token) {
    Date expiration = getExpirationDateFromToken(token);
    //判断过期时间是否在当前时间之前
    return expiration.before(new Date());
    } //JWT是否可以被刷新(过期就可以被刷新)
    public Boolean canTokenBeRefreshed(String token) {
    return isTokenExpired(token);
    } //刷新JWT
    public String refreshToken(String token) {
    String refreshedToken;
    try {
    //获得Token的Claims, 由于在生成JWT的时候会根据当前时间更新过期时间, 我们只需要手动修改
    //放在自定义属性中的创建时间就可以了
    Claims claims = getClaimsFromToken(token);
    claims.put(CLAIM_KEY_CREATED, new Date());
    //利用修改后的claims再次生成token, 就不需要我们每次都去查用户的信息和权限了
    refreshedToken = generateToken(claims);
    } catch (Exception e) {
    refreshedToken = null;
    }
    return refreshedToken;
    } //判断Token是否合法
    public Boolean validateToken(String token, UserDetails userDetails) {
    User user = (User) userDetails;
    String username = getUsernameFromToken(token);
    return (
    //如果用户名与token一致且token没有过期, 则认为合法
    username.equals(user.getUsername())
    && !isTokenExpired(token)
    );
    } }

    这里需要说明的是, 注意 getClaimsFromToken() 方法, 由于 JWT对于处于过期时间之外的TOKEN不会解析, 而会抛出异常, 因此我们不能使用统一的异常来返回空指针, 这样会导致我们无法进行TOKEN的刷新 (因为无法将过期的token中的用户名与我们长期存储, 如缓存中的用户名进行比对, 从而确定token的刷新策略生效)

  • 缓存仓库

    package com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityConfigUtil;
    
    import org.springframework.security.core.userdetails.User;
    import org.springframework.stereotype.Component; import java.util.HashMap;
    import java.util.Map; /**
    * 存入user token,可以引用缓存系统,存入到缓存。
    */
    @Component
    public class UserRepository { private static final Map<String, User> userMap = new HashMap<>(); public User findByUsername(final String username) {
    return userMap.get(username);
    } public User insert(User user) {
    userMap.put(user.getUsername(), user);
    return user;
    } public void remove(String username) {
    userMap.remove(username);
    }
    }

    此处偷懒, 写死在了代码里, 实际上我们可以使用Redis存储, 设定一个较长的过期时间

3. JWT过滤器

由于我们使用了JWT作为认证和授权, 因此每次请求都会受到一个前端请求的token, 我们这里把所有的请求都过一遍我们的JWT过滤器

package com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityFilter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.wang.spring_security_framework.common.SecurityConstant;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityConfigUtil.JWTUtil;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityConfigUtil.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map; //JWT校验过滤器
public class JwtFilter extends OncePerRequestFilter { @Autowired
private JWTUtil jwtUtil;
@Autowired
private UserRepository userRepository; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//从header中获取JWT
String jwtToken = request.getHeader(SecurityConstant.HEADER);
if (StrUtil.isNotBlank(jwtToken) && jwtToken.startsWith(SecurityConstant.TOKEN_SPLIT)) {
jwtToken = jwtToken.substring(SecurityConstant.TOKEN_SPLIT.length());
//如果去掉头部的"Bearer "后不为空
if (StrUtil.isNotBlank(jwtToken)) {
//获得当前用户名
String username = jwtUtil.getUsernameFromToken(jwtToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//从已有的user缓存中取了出user信息
User user = userRepository.findByUsername(username); //token相关信息的map
Map<String, String> resultMap = new HashMap<>();
//检查token是否有效
if (jwtUtil.validateToken(jwtToken, user)) {
//创建一个标识符, 表示此时Token有效, 不需要更新
resultMap.put("needRefresh", "false");
request.setAttribute("auth", resultMap);
//创建一个UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//设置用户登录状态 ==> 放到当前的Context中
SecurityContextHolder.getContext().setAuthentication(authentication);
} else if (username.equals(user.getUsername())) {
//如果用户名相同但是过期了, 刷新token (和缓存中的比较)
if (jwtUtil.canTokenBeRefreshed(jwtToken)) {
//TODO 将更新后的token更新到前端
String refreshedToken = jwtUtil.refreshToken(jwtToken);
resultMap.put("refreshedToken", refreshedToken);
//需要更新
resultMap.put("needRefresh", "true");
//将更新后的Token放到request中, 我们写一个controller, 从中取出后就可以更新了
request.setAttribute("auth", resultMap);
//创建一个UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//设置用户登录状态 ==> 放到当前的Context中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
}
}
// //创建一个UsernamePasswordAuthenticationToken
// UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities);
// //放到当前的Context中
// SecurityContextHolder.getContext().setAuthentication(token);
//继续过滤器链的请求
filterChain.doFilter(request, response);
}
}

这里需要注意

SpringSecurity的上下文用于存储用户的信息, 但是会在一个过滤器链执行完毕后就销毁

默认的是使用Session, SpringSecurity会从Session中拿到用户信息并放在上下文中, 我们这里用token, 放在用户本地, 因此每次校验之后都要生成一个上下文

在判断用户不为空且上下文为空可以保证我们位于一条新的过滤器链中(大概是为了防止并发抢占过滤器链)

4. 登录成功结果处理器

登录成功后, 与之前的不同的是, 我们要给前端传递一个生成的JWT

package com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityHandler;

import com.alibaba.fastjson.JSON;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityConfigUtil.JWTUtil;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityConfigUtil.UserRepository;
import com.wang.spring_security_framework.service.CaptchaService;
import com.wang.spring_security_framework.service.serviceImpl.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map; //登录成功处理
//我们不能在这里获得request了, 因为我们已经在前面自定义了认证过滤器, 做完后SpringSecurity会关闭inputStream流
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
CaptchaService captchaService;
@Autowired
JWTUtil jwtUtil;
@Autowired
UserRepository userRepository;
@Autowired
UserDetailServiceImpl userDetailsService; @Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//我们从自定义的认证过滤器中拿到的authInfo, 接下来做验证码校验和跳转, 以及JWT的生成
Map<String, String> authInfo = (Map<String, String>) request.getAttribute("authInfo");
System.out.println(authInfo);
System.out.println("success!");
String token = authInfo.get("token");
String inputCode = authInfo.get("inputCode"); //校验验证码
Boolean verifyResult = captchaService.versifyCaptcha(token, inputCode);
System.out.println(verifyResult); Map<String, String> result = new HashMap<>();
//验证码正确, 则生成JWT
if (verifyResult) {
//成功的跳转页面
String VerifySuccessUrl = "/newPage";
response.setHeader("Content-Type", "application/json;charset=utf-8");
result.put("code", "200");
result.put("msg", "认证成功!");
result.put("url", VerifySuccessUrl);
//JWT生成
String jwt = jwtUtil.JWTCreator(authentication);
result.put("jwt", jwt);
//用户信息放入缓存 ==> 从userDetailsService的实现类中根据用户名切除User类
userRepository.insert((User) userDetailsService.loadUserByUsername(authentication.getName()));
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(result));
} else {
String VerifyFailedUrl = "/toLoginPage";
response.setHeader("Content-Type", "application/json;charset=utf-8");
result.put("code", "201");
result.put("msg", "验证码输入错误!");
result.put("url", VerifyFailedUrl);
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(result));
}
}
}

5. SpringSecurity配置类

package com.wang.spring_security_framework.config;

import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityFilter.JwtFilter;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityFilter.MyCustomAuthenticationFilter;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityHandler.LoginFailHandler;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityHandler.LoginSuccessHandler;
import com.wang.spring_security_framework.config.SpringSecurityConfig.SpringSecurityHandler.LogoutHandler;
import com.wang.spring_security_framework.service.UserService;
import com.wang.spring_security_framework.service.serviceImpl.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter; //SpringSecurity设置
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Autowired
UserDetailServiceImpl userDetailServiceImpl;
@Autowired
LoginSuccessHandler loginSuccessHandler;
@Autowired
LoginFailHandler loginFailHandler;
@Autowired
LogoutHandler logoutHandler; //授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//指定自定义的登录页面, 表单提交的url, 以及成功后的处理器
http.
formLogin()
.loginPage("/toLoginPage")
.and().csrf().disable().cors(); //退出登录
http
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutHandler)
//退出时让Session无效
.invalidateHttpSession(true); //设置过滤器链, 添加自定义过滤器
http
.addFilter(myCustomAuthenticationFilter())
.addFilterBefore(jwtFilter(), LogoutFilter.class); //允许iframe
http
.headers().frameOptions().sameOrigin(); //授权
http
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers("/r/r2").hasAnyAuthority("p2")
.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')")
.antMatchers("/r/**").authenticated().anyRequest().permitAll(); http
// 基于token,所以不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); } //注册自定义过滤器
@Bean
MyCustomAuthenticationFilter myCustomAuthenticationFilter() throws Exception { MyCustomAuthenticationFilter filter = new MyCustomAuthenticationFilter(); //设置过滤器认证管理
filter.setAuthenticationManager(super.authenticationManagerBean());
//设置filter的url
filter.setFilterProcessesUrl("/login");
//设置登录成功处理器
filter.setAuthenticationSuccessHandler(loginSuccessHandler);
//设置登录失败处理器
filter.setAuthenticationFailureHandler(loginFailHandler); return filter;
} //注册JWT过滤器
@Bean
public JwtFilter jwtFilter() throws Exception {
return new JwtFilter();
} //密码使用盐值加密 BCryptPasswordEncoder
//BCrypt.hashpw() ==> 加密
//BCrypt.checkpw() ==> 密码比较
//我们在数据库中存储的都是加密后的密码, 只有在网页上输入时是明文的
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} }

在配置类中, 我们主要添加了以下的工作

  • 注册JWT过滤器

  • 禁用session, 同时由于禁用了session, 不会有csrf攻击, 我们就大胆的禁用防csrf攻击吧

  • 注册JWT过滤器到过滤器链中

    • 这里需要说明的是, 我们将 JWT 过滤器放在了logout过滤器之前, 这是由源码的过滤器链决定的

      ​ 可以看到, logout过滤器甚至比Username校验过滤器还靠前, 因此我们将其注册在logout过滤器之前

6. 添加Controller

  • 由于我们采用本地存储token的策略, 因此不能从session获得用户的信息了, 而SpringSecurity整合Thymeleaf的方言是从Session获得用户的信息的, 因此我们要写一个发送用户名的Controller

    @RequestMapping("/username")
    public String userName() {
    return JSON.toJSONString(getUserName());
    }
  • 此处采用的策略是后台发请求返回判断JWT是否需要刷新 (其实更合理的方法是在前端的所有请求都异步的走一遍下面的url)

    package com.wang.spring_security_framework.controller;
    
    import com.alibaba.fastjson.JSON;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; //用于刷新
    @RestController
    public class RefreshController {
    //刷新token ==> 如果JWT过期
    @RequestMapping("/refreshJWT")
    public String refreshJWT(HttpServletRequest request) {
    return JSON.toJSONString(request.getAttribute("auth"));
    }
    }

7. 后端总结

至此, 我们完成了后端对于JWT的处理

笔者得到的最重要的一个体会就是, SpringSecurity过滤器链如果想附加什么东西上去, 用addAttribute加到request上就好了, 我们在Controller中取出对应的getAttribute的值

后端的结构如下

8. 前端代码

笔者学习过程中, 发现大部分的代码都是在postman中测试了接口, 很少有人将前端整合写出, 因此笔者踩坑也花了不少精力, 来看看吧!

1. 写在前面的话

首先, 我们使用JWT应该把它放在header里, 这就有一个问题, 我们如何将每个请求都设置header

其中最棘手的是ajax的回调函数, 经过一天的尝试, 笔者发现有以下三种解决方法

  • axios ==> 强烈推荐

  • 写xhr

  • 使用ajaxhook(github有开源的文档)

笔者采用的是前两种

JWT 请求头前要加一个 "Bearer " ==> 这是JWT的规定, 其实不加也可以~~

本文采用的是存储在本地的 localstorage 中, 本地存储的策略有很多, 笔者只是一个后端萌新, 前端一窍不通, 就不在此处深究了

2. 采用axios统一处理请求头

function logout() {
layui.use('layer', function () {
//退出登录
layer.confirm('确定要退出么?', {icon: 3, title: '提示'}, function (index) {
//do something
let url = '/logout'; //添加request拦截器, 为所有请求添加header
axios.interceptors.request.use(function (config) {
//请求头要这样添加
config.headers['accessToken'] = "Bearer " + localStorage.getItem("jwt");
return config;
}, function (error) {
return Promise.reject(error);
}); //注意, axios的回调函数和ajax不一样, response是一个封装的结果, 要用response.data.xxx才能得到结果
axios({
method: 'post',
url: url,
responseType: 'json',
responseEncoding: 'utf8',
headers: {
"accessToken": ("Bearer " + localStorage.getItem("jwt"))
}
})
.then(function (response) {
alert("进入success---");
let code = response.data.code;
let url = response.data.url;
let msg = response.data.msg;
if (code === '203') {
alert(msg);
//清除jwt
localStorage.removeItem("jwt");
//清除username
localStorage.removeItem("username");
//跳转
window.location.href = url;
} else {
alert("未知错误!");
}
})
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
layer.close(index);
});
});
}

注意, axios和ajax不一样, 参数是封装好的, 切记!

同时注意请求头的写法

这里的思路是使用拦截器, 但是拦截器只能拦截axios的then和catch回调函数, 不能拦截全部请求

退出登录记得清除本地不需要的内容

3. 采用xhr统一处理请求头

$.ajaxSetup({
type: "post",
dataType: "json",
contentType: "application/json;charset=utf-8",
success: function (data) {
let code = data.code;
let url = data.url;
let msg = data.msg;
if (code == 204) {
alert(msg);
let xhr = new XMLHttpRequest();
xhr.open("get", url);
xhr.setRequestHeader("accessToken", "Bearer " + localStorage.getItem("jwt"));
// window.location.href = url;
xhr.send(null);
//回调函数, 这里一定要同时判断两个状态, 否则会多次执行(与xhr的原理有关)
xhr.onreadystatechange = function () {
//判断xhr的状态以及http的状态, 发送完毕且200才执行
if(xhr.readyState == 4 && xhr.status == 200) {
data = xhr.responseText;
alert(data);
} else if (xhr.readyState == 4 && xhr.status == 403){
//403 ==> 没有权限的响应
alert("没有权限!")
}
} } else {
alert("未知错误!" + code + url);
}
},
error: function (xhr, textStatus, errorThrown) {
alert("进入error---");
alert("状态码:" + xhr.status);
alert("状态:" + xhr.readyState); //当前状态,0-未初始化,1-正在载入,2-已经载入,3-数据进行交互,4-完成。
alert("错误信息:" + xhr.statusText);
alert("返回响应信息:" + xhr.responseText);//这里是详细的信息
alert("请求状态:" + textStatus);
alert(errorThrown);
alert("请求失败");
},
//请求头中放入JWT
beforeSend: function (request) {
request.setRequestHeader("accessToken", "Bearer " + localStorage.getItem("jwt"));
},
}); function btnClick1() {
let url = "/toR1";
$.ajax({
url: url
});
} function btnClick2() {
let url = "/toR2";
$.ajax({
url: url
});
} function btnClick3() {
let url = "/toR3";
$.ajax({
url: url
});
} //页面加载时就调用, 将用户名存放在localstorage中(注意语法)
$(function () {
$.ajax({
type: "post",
dataType: "json",
contentType: "application/json;charset=utf-8",
url: '/r/username',
//请求头中放入JWT
beforeSend: function (request) {
request.setRequestHeader("accessToken", "Bearer " + localStorage.getItem("jwt"));
},
success: function (data) {
localStorage.username = data;
}
});
});

注意

使用 xhr 添加请求头, 一定要写全open和send, 而且位置不能错, 否则无效

这种做法本质上是原生的ajax(不是jQuery封装后的)

要注意, 设置了响应的格式为 JSON, 后端不要传错, 否则会走到error的回调函数中

ajax无法使用页面后端跳转, 因为他是局部刷新的, 要ajax从前端跳转**

我们在页面一加载就去请求用户名, 并放在本地, 这样我们就不需要频繁的去取了

4. 刷新token

此处在页面加载完毕后执行两个操作

  • 渲染username
  • 请求后端接口, 看JWT是否过期, 过期就放一个新的到本地 (每500ms一次)
//页面加载完毕后执行, 显示username ==> 从localstorage中取
$(window).load(function () {
let username = localStorage.getItem("username");
$("#showUsername").text(username);
JWTRefreshCheck();
}); //定时询问JWT是否过期 ==> 每500毫秒发送请求
function JWTRefreshCheck() {
setInterval(function () {
$.ajax({
type: "post",
dataType: "json",
contentType: "application/json;charset=utf-8",
url: '/refreshJWT',
//请求头中放入JWT
beforeSend: function (request) {
request.setRequestHeader("accessToken", "Bearer " + localStorage.getItem("jwt"));
},
success: function (data) {
if (data.needRefresh == true) {
localStorage.setItem("jwt", data.refreshedToken);
}
}
});
},
500);
}

9. 写在最后的话

终于, 笔者的SpringSecurity学习可以告一段落了, 关于认证, 授权以及验证码的生成可以在前面的文章中找到

代码位于github, 欢迎参考和交流!

https://github.com/hello-world-cn/My-SpringSecurity-Framework

SpringSecurity之整合JWT的更多相关文章

  1. SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. SpringSecurity 整合 JWT

    项目集成Spring Security(一) 在上一篇基础上继续集成 JWT ,实现用户身份验证. 前言 前后端分离项目中,如果直接把 API 接口对外开放,我们知道这样风险是很大的,所以在上一篇中我 ...

  3. Spring Security整合JWT,实现单点登录,So Easy~!

    前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...

  4. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  5. Spring Boot初识(4)- Spring Boot整合JWT

    一.本文介绍 上篇文章讲到Spring Boot整合Swagger的时候其实我就在思考关于接口安全的问题了,在这篇文章了我整合了JWT用来保证接口的安全性.我会先简单介绍一下JWT然后在上篇文章的基础 ...

  6. laravel 5.5 整合 jwt 报错Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist解决

    今天介绍一个在laravel5.5新版本整合jwt  执行 php artisan jwt:generate 再生成密钥时报的一个错误 Method Tymon\JWTAuth\Commands\JW ...

  7. 教你 Shiro + SpringBoot 整合 JWT

    本篇文章将教大家在 shiro + springBoot 的基础上整合 JWT (JSON Web Token) 如果对 shiro 如何整合 springBoot 还不了解的可以先去看我的上一篇文章 ...

  8. 玩转 SpringBoot 2 之整合 JWT 下篇

    前言 在<玩转 SpringBoot 2 之整合 JWT 上篇> 中介绍了关于 JWT 相关概念和JWT 基本使用的操作方式.本文为 SpringBoot 整合 JWT 的下篇,通过解决 ...

  9. 玩转 SpringBoot 2 之整合 JWT 上篇

    前言 该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT. 介绍前在这里我们来探讨一下如何学习一门新的技术, ...

随机推荐

  1. Cocos2d-x extensions库使用问题解决方法

    需要在加入头文件#include "cocos-ext.h" 1>e:\cocos\cocos2d-x\cocos2d-x-3.10\extensions\gui\cccon ...

  2. Hadoop框架:HDFS高可用环境配置

    本文源码:GitHub·点这里 || GitEE·点这里 一.HDFS高可用 1.基础描述 在单点或者少数节点故障的情况下,集群还可以正常的提供服务,HDFS高可用机制可以通过配置Active/Sta ...

  3. Win32之创建线程

    0x01.什么是线程? 1.线程是附属在进程上的执行实体,是代码的执行流程 进程 本身是空间上的概念,代表4GB的虚拟内存,线程代表着时间概念,也就是说,线程是当前运行的代码 在某个时间点只能有一段代 ...

  4. 系统日志报错i8042prt无法加载

    原因如下: 解决方法为: 此报错可以直接忽略,不过由此可能导致即插即用(plugplay)报错,在即插即用报错时,重启服务器即可.

  5. Scrapy加Redis加IP代理池实现音乐爬虫

    音乐爬虫 关注公众号"轻松学编程"了解更多. 目的:爬取歌名,歌手,歌词,歌曲url. 一.创建爬虫项目 创建一个文件夹,进入文件夹,打开cmd窗口,输入: scrapy star ...

  6. JavaWeb项目问题记录

    模板 [遇到的问题] [时间] [原因] [解决方案] [排查思路及方式] 思路: 1) 2) [遇到的问题] 品优购项目中运营商页面查询广告信息是,无法正常查询,错误如下: Failed to lo ...

  7. layui table中固定表头,弹框缩放之后,表头对不齐问题

    新手一枚  直接上解决方案 在layui弹出成功后再渲染表格数据 具体操作就是在layer弹层完成之后的回调中渲染表格数据 layer.open({ type: 1, content:  $(&quo ...

  8. unicode与编码的关系

    参考链接先贴上来:https://blog.csdn.net/humadivinity/article/details/79403625https://www.cnblogs.com/kevin2ch ...

  9. 3、编程语言与Python介绍

    一 引子 基于上一章所学,有了计算机硬件,再在硬件之上安装好操作系统,我们就有了一个应用程序的运行平台,我们接下来的任务就是学习如何使用某款编程语言来开发应用程序. 本章的主题是先了解一下编程语言,然 ...

  10. 正式班D26

    2020.11.11星期三 正式班D26 目录 14.2.2 ifconfig命令 14.2.2 ifconfig命令 ifconfig命令结果解释 [root@ccc ~]# ifconfig et ...