0
点赞
收藏
分享

微信扫一扫

Linux Zookeeper 安装

殇感故事 2024-04-13 阅读 17

1.实现登录

这种情况我们想到了: 令牌技术

2.令牌技术

令牌其实就是⼀个⽤⼾⾝份的标识, 名称起的很⾼⼤上, 其实本质就是⼀个字符串.

2.1令牌的优缺点

优点:

解决了集群环境下的认证问题

减轻服务器的存储压⼒(⽆需在服务器端存储)

缺点:

需要⾃⼰实现(包括令牌的⽣成, 令牌的传递, 令牌的校验)

2.2 JWT令牌 

令牌本质就是⼀个字符串, 他的实现⽅式有很多, 我们采⽤⼀个JWT令牌来实现.

2.2.1 JWT组成

2.3 JWT令牌生成和校验

1. 引⼊JWT令牌的依赖
2. 使⽤Jar包中提供的API来完成JWT令牌的⽣成和校验
2.1 生成令牌
@SpringBootTest
public class JwtUtilsTest {
    //过期毫秒时⻓ 30分钟
    public static final long Expiration=30*60*1000;
    //密钥
    private static final String
            secretString="BhIDH5ISHd9c4cX/GMpP8ONEZ9edrGKyWmO7wpHnZFk=";
    //⽣成安全密钥
    private static final SecretKey KEY =
            Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));
    @Test
    public void genJwt(){
        //⾃定义信息
        Map<String,Object> claim = new HashMap<>();
        claim.put("id",1);
        claim.put("username","zhangsan");
        String jwt = Jwts.builder()
                .setClaims(claim) //⾃定义内容(负载)
                .setIssuedAt(new Date())// 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() +
                        Expiration)) //设置过期时间
                .signWith(KEY) //签名算法
                .compact();
        System.out.println(jwt);
    }

    /**⽣成密钥*/
    @Test
    public void genKey(){
        Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String secretString = Encoders.BASE64.encode(key.getEncoded());
        System.out.println(secretString);
    }
}

 输出的内容, 就是JWT令牌通过点(.)对三个部分进⾏分割, 我们把⽣成的令牌通过官⽹进⾏解析, 就可以看到我们存储的信息了

2.2 校验令牌 

完成了令牌的⽣成, 我们需要根据令牌, 来校验令牌的合法性(以防客⼾端伪造)

@Test
    public void parseJWT() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsImlhdCI6" +
                "MTcxMjkyOTk4NywiZXhwIjoxNzEyOTMxNzg3fQ.hDyyGBxcv959gtZBX8MBy5JshP__pgtNsSZsoxO0SCo\n";
        //创建解析器, 设置签名密钥
        JwtParserBuilder jwtParserBuilder =
                Jwts.parserBuilder().setSigningKey(KEY);
        //解析token
        Claims claims = jwtParserBuilder.build().parseClaimsJws(token).getBody();
        System.out.println(claims);
    }

令牌解析后, 我们可以看到⾥⾯存储的信息,如果在解析的过程当中没有报错,就说明解析成功了

3.用户的登录

3.1 约定前后端交互接口

 3.2 实现服务器代码

3.2.1 创建JWT⼯具类

package com.example.demo.utils;

import com.example.demo.constants.Constant;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
import java.util.Map;


@Slf4j
public class JwtUtils {
    //一、法一:自定义生成密钥的办法(自定义必须满足base64编码后字节长度>=256 bits)
    //1.密钥
    public static String key="HAA/HKhIBFZaj9Ipcw7CDKKf8M1BG3TxychLG+INjcs=";
    //2.过期的时间(单位毫秒)->30min
    public static long expiration = 30*60*1000;
    //3.根据密钥生成安全密钥(需要先对字符串进行BASE64编码才可以设置密钥,自定义密钥需要有足够的长度)
    private static SecretKey secretKey= Keys.hmacShaKeyFor(Decoders.BASE64.decode(key));
    //二、法二:生成密钥
    //SecretKey key = Jwts.SIG.HS256.key().build();

    //生成令牌
    //(1)设置令牌中携带的内容
    public static String genJwt(Map<String, Object> claim){
        //签名算法
        String jwt = Jwts.builder()
                .setClaims(claim) //⾃定义内容(载荷)
                .setIssuedAt(new Date())// 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() +
                        expiration)) //设置过期时间
                .signWith(secretKey) //签名算法(密钥,加密算法)
                .compact();//返回为字符串类型的jwt令牌
        return jwt;
    }

    /**
     * 解析令牌
     * @param token
     * @return
     */
    public static Claims parseToken(String token){
        //创建解析器, 设置签名密钥
        JwtParserBuilder jwtParserBuilder =
                Jwts.parserBuilder().setSigningKey(secretKey);
        Claims body = null;
        try {
            body=jwtParserBuilder.build().parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            log.error("token过期, 校验失败, token:",token);

        } catch (Exception e) {
            log.error("token校验失败, token:",token);
        }
        return body;
    }
    //校验令牌
    public static boolean checkToken(String token){
        Claims body = parseToken(token);
        if (body==null){
            return false;
        }
        return true;
    }
    //从token中获取用户id
    public static Integer getUserIdFromToken(String token){
        Claims body = parseToken(token);
        if (body!=null){
            return (Integer) body.get(Constant.USER_CLAIM_ID);
        }
        return null;
    }
}

3.2.2.service包

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public UserInfo selectByName(String userName){
        return  userInfoMapper.selectByName(userName);
    }
}

3.2.3.controller包

package com.example.demo.controller;

import com.example.demo.constants.Constant;
import com.example.demo.model.Result;
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import com.example.demo.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public Result login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                        String userName, String password){
        if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){
            log.error("userName"+userName+",password"+password);
            return Result.fail(Constant.RESULT_CODE_FAIL,"用户或密码为空");
        }
        //判断账号密码是否正确
        UserInfo userInfo=userService.selectByName(userName);
        if(userInfo==null||!userInfo.getPassword().equals(password)){
            return Result.fail(Constant.RESULT_CODE_FAIL,"用户或密码输入错误");
        }
        //登陆成功
        Map<String,Object> claims=new HashMap<>();
        claims.put(Constant.USER_CLAIM_ID,userInfo.getId());
        claims.put(Constant.USER_CLAIM_NAME,userInfo.getUserName());
        String token= JwtUtils.genJwt(claims);
        System.out.println("生成token"+token);
        return Result.success(token);
    }
}

3.2.4 测试后端接口

成功!!!

3.2.5  前端代码修改

<script>
        function login() {
            $.ajax({
                type: "get",
                url: "/user/login",
                data: {
                    userName: $("#username").val(),
                    password: $("#password").val()
                },
                success: function(result){
                    console.log(result);
                    if(result.code==200&&result.data!=null){
                        localStorage.setItem("user_token",result.data);
                        location.assign("blog_list.html");
                    }else{
                        alert("账号或密码有误");
                        return;
                    }
                }
            });
        }
    </script>

验证成功!!!

 3.3 location.href=url 、location.assign(url) 、location.replace(url) 、location.reload()

  • location.href=url 效果类似 location.assign(url) , 相当于跳转新页面, 可以后退
  • location.replace(url) , 是改变当前页面的地址, 不能后退
  • location.reload() , 作用是刷新, 没有参数

location.href=url 和 location.assign(url) 和 location.replace(url) 和 location.reload()_location href mdn-CSDN博客

4.实现强制登陆操作(拦截器) 

当⽤⼾访问 博客列表⻚ 和 博客详情⻚ 时, 如果⽤⼾当前尚未登陆, 就⾃动跳转到登陆⻚⾯.

我们可以采⽤拦截器来完成, token通常由前端放在header中, 我们从header中获取token, 并校验token是否合法

1.注册拦截器

package com.example.demo.config;

import com.example.demo.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从header中获取token
        String jwtToken=request.getHeader("user_token");
        log.info("从header中获取token:{}",jwtToken);
        //验证⽤⼾token
        Claims claims = JwtUtils.parseToken(jwtToken);
        if (claims!=null){
            log.info("令牌验证通过, 放⾏");
            return true;
        }
        response.setStatus(401);
        return true;
    }
}

2.定义拦截器

package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.List;


@Configuration
public class AppConfig implements WebMvcConfigurer {
    private final List excludes = Arrays.asList(
            "/**/*.html",
            "/blog-editormd/**",
            "/css/**",
            "/js/**",
            "/pic/**",
            "/login"
    );
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludes);
    }
}

3.实现客户端代码

1) 前端请求时, header中统⼀添加token, 可以写在common.js中

$(document).ajaxSend(function (e, xhr, opt) {
    var user_token = localStorage.getItem("user_token");
    xhr.setRequestHeader("user_token", user_token);
});

2) 修改 blog_datail.html和blog_list.html

访问⻚⾯时, 添加失败处理代码

使⽤ location.assign 进⾏⻚⾯跳转.

4.测试

发现一直被拦截,时由于方法调用错误导致的。

举报

相关推荐

0 条评论