0


使用JWT生成token实现权限验证

流程

  1. 点击登录按钮,后端验证账号密码是否通过,如果通过则生成token,把token发送给前端,前端保存到cookie(前后端分离是不能使用保存session,因为每次发送ajax请求响应后都会断开服务器,就会导致session生命周期就销毁掉,然后再发送请求时再重新连接服务器,session已经不存在了),之后访问受限资源就需要取cookie拿到token,然后作为参数(放在请求头更安全)发送给后端,后端验证token

Jwt介绍

Jwt是由三部分组成的字符串(header头部,payload载荷,signature签名)

  1. 头部:用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个 JSON 对象。例如:
  2. {
  3. "typ": "JWT",
  4. "alg": "HS256"
  5. }
  6. 载荷:其实就是自定义的数据,一般存储用户 Id,过期时间等信息。也就是 JWT 的核心所在,因为这些数据就是使后端知道此 token 是哪个用户已经登录的凭证。而且这些数据是存在 token 里面的,由前端携带,所以后端几乎不需要保存任何数据。
  7. 例如:
  8. {
  9. "uid": "xxxxidid", //用户id
  10. "exp": "12121212" //过期时间
  11. }
  12. 签名:1.头部和载荷 各自base64加密后用.连接起来,然后就形成了 xxx.xx 的前两段 token2.最后一段 token 的形成是,前两段加入一个密匙用 HS256 算法或者其他算法加密形成。3. 所以 token3 段的形成就是在签名处形成的。

将这三部分用.连接成一个完整的字符串,构成了最终的jwt

一、添加依赖

  1. //提供JWT的java类
  2. <dependency>
  3. <groupId>com.auth0</groupId>
  4. <artifactId>java-jwt</artifactId>
  5. <version>3.10.3</version>
  6. </dependency>
  7. //提供JWT算法
  8. <dependency>
  9. <groupId>io.jsonwebtoken</groupId>
  10. <artifactId>jjwt</artifactId>
  11. <version>0.9.1</version>
  12. </dependency>

二、给登录按钮添加单击事件@click="doSubmit"。给账号文本框密码文本框都添加@blur="checkInfo"事件。发送请求,接收响应信息

重点代码: setCookieValue("token",vo.msg) 调用自定义方法setCookieValue把token放到cookie

  1. <script type="text/javascript">
  2. var baseUrl = "http://localhost:8080/";
  3. var vm = new Vue({
  4. el:"#container",
  5. data:{
  6. username:"",
  7. password:"",
  8. tips:"",
  9. colorStyle:"color:red",
  10. isRight:false,
  11. },
  12. methods:{
  13. doSubmit:function(){
  14. if(vm.isRight){
  15. var url = baseUrl+"user/login";
  16. axios.get(url,{
  17. params:{
  18. username:vm.username,
  19. password:vm.password
  20. }
  21. }).then((res)=>{
  22. //res.data 才表示接口返回的数据
  23. var vo = res.data;
  24. if(vo.code == 10000){
  25. //如果登录成功,就把token存储到cookie,后端是把token放在msg里
  26. setCookieValue("token",vo.msg);
  27. window.location.href="index.html";
  28. }else{
  29. vm.tips = "登录失败,账号或密码错误!"
  30. }
  31. });
  32. }else{
  33. vm.tips = "请正确输入帐号和密码!";
  34. }
  35. },
  36. checkInfo:function(){
  37. if(vm.username == ""){
  38. vm.tips = "请输入帐号!";
  39. vm.isRight = false;
  40. }else if(vm.username.length<8 || vm.username.length>20){
  41. vm.tips = "账号长度必须为8-20个字符!";
  42. vm.isRight = false;
  43. }else{
  44. //账号合法,校验密码
  45. if(vm.password == ""){
  46. vm.tips = "请输入密码!";
  47. vm.isRight = false;
  48. }else if(vm.password.length<6 || vm.password.length>16){
  49. vm.tips = "密码长度必须为6-16个字符!";
  50. vm.isRight = false;
  51. }else{
  52. vm.tips ="";
  53. vm.isRight = true;
  54. }
  55. }
  56. }
  57. }
  58. });
  59. </script>

三、Controller接收请求

  1. @GetMapping("/user/login")
  2. public ResultVO login(@RequestParam("username") String name,
  3. @RequestParam("password") String pwd){
  4. return userService.checkLogin(name,pwd);
  5. }

四、Service和ServiceImpl处理业务、生成token

  1. public interface UserService {
  2. public ResultVO checkLogin(String name, String pwd);
  3. }
  1. @Service
  2. @Scope("singleton")//singleton单例模式,全局有且仅有一个实例
  3. public class UserServiceImpl implements UserService {
  4. @Resource
  5. private UsersMapper usersMapper;
  6. @Override
  7. public ResultVO checkLogin(String name, String pwd) {
  8. //使用tkmapper查询user 你们用自己写的mapper也可以
  9. Example example = new Example(Users.class);
  10. Example.Criteria criteria = example.createCriteria();
  11. criteria.andEqualTo("username",name);
  12. List<Users> users = usersMapper.selectByExample(example);
  13. if(users.size()==0){
  14. return new ResultVO(10001,"登录失败,用户名不存在",null);
  15. }else {
  16. Users user = users.get(0);
  17. String md5Pwd = MD5Utils.md5(pwd);
  18. // 数据库密码是MD5加密过的,MD5算法又不可逆,所以只能把登录密码加密后去和数据库的密码比对
  19. if(user.getPassword().equals(md5Pwd)){
  20. //如果登录成功,生成token
  21. JwtBuilder builder = Jwts.builder();
  22. HashMap<String,Object> map = new HashMap<>();
  23. map.put("key1","value1");
  24. map.put("key2","value2");
  25. String token = builder.setSubject(name) //载荷部分,主题,就是token中携带的数据,这里把用户名放进去
  26. .setIssuedAt(new Date()) //设置token的生成时间
  27. .setId(users.get(0).getUserId() + "") //设置用户id为token id ''是因为用户id是int类型,需要转换为字符串类型
  28. .setClaims(map) //map中可以存放用户的角色权限信息
  29. .setExpiration(new Date(System.currentTimeMillis() + 24*60*60*1000)) //设置token过期时间,当前时间加一天就是时效为一天过期
  30. .signWith(SignatureAlgorithm.HS256, "ycj123456") //签名部分,设置HS256加密方式和加密密码,ycj123456是自定义的密码
  31. .compact();
  32. return new ResultVO(10000,token,user);//把token封装到ResultVO传到前端。
  33. }else {
  34. return new ResultVO(10001,"密码错误",null);
  35. }
  36. }
  37. }
  38. }

五、MD5、返回的工具类

  1. package com.ycj.utils;
  2. import java.math.BigInteger;
  3. import java.security.MessageDigest;
  4. import java.security.NoSuchAlgorithmException;
  5. //MD5 生成器
  6. public class MD5Utils {
  7. public static String md5(String password){
  8. //生成一个md5加密器
  9. try {
  10. MessageDigest md = MessageDigest.getInstance("MD5");
  11. //计算MD5 的值
  12. md.update(password.getBytes());
  13. //BigInteger 将8位的字符串 转成16位的字符串 得到的字符串形式是哈希码值
  14. //BigInteger(参数1,参数2) 参数1 是 1为正数 0为零 -1为负数
  15. return new BigInteger(1, md.digest()).toString(16);
  16. } catch (NoSuchAlgorithmException e) {
  17. e.printStackTrace();
  18. }
  19. return null;
  20. }
  21. }
  1. public class ResStatus {
  2. public static final int OK=10000;
  3. public static final int NO=10001;
  4. public static final int LOGIN_SUCCESS = 2000; //认证成功
  5. public static final int LOGIN_FAIL_NOT = 20001; //用户未登录
  6. public static final int LOGIN_FAIL_OVERDUE = 20002; //用户登录失效
  7. }
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class ResultVO {
  5. private int code;
  6. private String msg;
  7. private Object data;
  8. }

六、Dao查询用户信息

这里是使用tkMapper,selectByExample()就是它提供的,你们也可以自己写mapper

  1. public interface UsersMapper extends Mapper<User>,MySqlMapper<User> {
  2. //继承了Mapper<T>,MySqlMapper<T>就不用写dao和mapper了
  3. }

七、从cookie中存取token

  1. //cookie只能存放键值对
  2. var operator = "=";
  3. //window.document.cookie可以拿到cookie所有的key=value;形式的字符串。所以从cookie拿值,遍历cookie的所有key,直到key等于keyStr,
  4. //就可以拿到对应的值,例如我们要拿名为token的key,调用方法getCookieValue(token)就可以拿到key为token的值(value)
  5. function getCookieValue(keyStr){
  6. var value = null;
  7. var s = window.document.cookie;
  8. var arr = s.split("; ");
  9. for(var i=0; i<arr.length; i++){
  10. var str = arr[i];
  11. var k = str.split(operator)[0];
  12. var v = str.split(operator)[1];
  13. if(k == keyStr){
  14. value = v;
  15. break;
  16. }
  17. }
  18. return value;
  19. }
  20. //往cookie中设置格式:document.cookie = key=value,例如token=fohweoif2n334023noi2r
  21. function setCookieValue(key,value){
  22. document.cookie = key+operator+value;
  23. }

八、携带token访问受限资源示例,发送请求,接收响应信息

重点代码:var token = getCookieValue("token"); headers:{token:token //访问受限资源必须把token传到后端校验},

  1. <script type="text/javascript">
  2. var vm = new Vue({
  3. el:"#container",
  4. data:{
  5. token:null,
  6. },
  7. created:function(){
  8. var token = getCookieValue("token");
  9. if(token == null){
  10. var loginUrl = "login.html?tips=请先登录!&returnUrl=shopcart.html";//returnUrl是登录成功后重新返回这个页面的url
  11. window.location.href = encodeURI(loginUrl);
  12. }else{
  13. //请求当前用户的购物车记录
  14. this.token = token;//this.token是data中的token
  15. var userId = getCookieValue("userId");
  16. var url = baseUrl+"shopcart/list";
  17. axios({
  18. url:url,//写受限资源的url
  19. method:"get",
  20. headers:{
  21. token:token //访问受限资源必须把token传到后端校验
  22. },
  23. params:{
  24. userId:userId //这里userId是我访问受限资源所需要的,你们根据自己的需求
  25. }
  26. }).then((res)=>{
  27. console.log(res.data);
  28. if(res.data.code==20002 || res.data.code==20001){
  29. //token过期或未登录
  30. var loginUrl = "login.html?tips=请先登录!&returnUrl=shopcart.html";
  31. window.location.href = encodeURI(loginUrl);
  32. }else{
  33. //请求成功,拿到数据,进行渲染页面
  34. this.shopcarts = res.data.data;
  35. }
  36. });
  37. }
  38. },
  39. });
  40. </script>

九、设置拦截器

如果受限资源有多个,我们可以设置拦截器去校验token,就不用每次都去校验一次

配置拦截路径

  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3. @Autowired
  4. private CheckTokenInterceptor checkTokenInterceptor;
  5. @Override
  6. public void addInterceptors(InterceptorRegistry registry) {
  7. registry.addInterceptor(checkTokenInterceptor)
  8. .addPathPatterns("/shopcart/**")
  9. .addPathPatterns("/orders/**")
  10. .addPathPatterns("/useraddr/**")
  11. .addPathPatterns("/user/check");
  12. }

拦截并解析token

  1. @Component
  2. public class CheckTokenInterceptor implements HandlerInterceptor {
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. //关于浏览器的请求预检.在跨域的情况下,非简单请求会先发起一次空body的OPTIONS请求,称为"预检"请求,用于向服务器请求权限信息,等预检请求被成功响应后,才发起真正的http请求。
  6. String method = request.getMethod();
  7. if("OPTIONS".equalsIgnoreCase(method)){
  8. return true;
  9. }
  10. // String token = request.getParameter("token");放入params才能用这个,放hearder用getHearder
  11. String token = request.getHeader("token");
  12. if(token == null){
  13. ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
  14. doResponse(response,resultVO);
  15. }else{
  16. try {
  17. JwtParser parser = Jwts.parser();
  18. parser.setSigningKey("ycj123456"); //解析token的SigningKey必须和生成token时设置密码一致
  19. //如果token检验通过(密码正确,有效期内)则正常执行,否则抛出异常
  20. Jws<Claims> claimsJws = parser.parseClaimsJws(token);
  21. return true;//true就是验证通过,放行
  22. }catch (ExpiredJwtException e){
  23. ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_OVERDUE, "登录过期,请重新登录!", null);
  24. doResponse(response,resultVO);
  25. }catch (UnsupportedJwtException e){
  26. ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "Token不合法,请自重!", null);
  27. doResponse(response,resultVO);
  28. }catch (Exception e){
  29. ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
  30. doResponse(response,resultVO);
  31. }
  32. }
  33. return false;
  34. }
  35. //没带token或者检验失败响应给前端
  36. private void doResponse(HttpServletResponse response,ResultVO resultVO) throws IOException {
  37. response.setContentType("application/json");
  38. response.setCharacterEncoding("utf-8");
  39. PrintWriter out = response.getWriter();
  40. String s = new ObjectMapper().writeValueAsString(resultVO);
  41. out.print(s);
  42. out.flush();
  43. out.close();
  44. }
  45. }

如果token校验通过,返回true,拦截器会放行,可以访问受限资源。否则,使用doResponse(response,resultVO)响应信息给前端页面。

标签: java spring 后端

本文转载自: https://blog.csdn.net/m0_59359926/article/details/123809705
版权归原作者 给你一朵小红花H 所有, 如有侵权,请联系我们删除。

“使用JWT生成token实现权限验证”的评论:

还没有评论