目录
一,令牌技术
1,不使用session的原因:
2,有两种解决方案:
(1)服务器层面的
(2)客户端层面的(JWT令牌)
生成签名:
生成jwt令牌:
验证令牌是否合法:
(3)令牌实际运用
二,加密加盐:
进行加密:
进行验证:
一,令牌技术
1,不使用session的原因:
登录页面,用户会将密码和账号传给服务器,服务器会进行校验并将校验结果传给前端,如果密码和账号正确服务器就会创建session,通过Cookie把sessionId返回给浏览器,但是一般情况下session都是储存在内存中的,当只有一台机子的时候,可以正常使用session来验证用户是否登录,但是在企业开发中通常是使用集群(单台机子如果如果一旦出现问题,就会整个无法访问了,为了保证服务器的稳定性,通常会将web应用部署到多个服务器上,并进行负载均衡,使用户的请求分配到不同的服务器上),集群环境无法部署session.
集群环境无法部署Session的原因,当用户发出请求代某一台服务器的时候,这台服务器会将session储存在这台服务器上,但是当用户再次访问服务器的时候,会被分配到其他的服务器上,但是此时,这台机子并没有用户的Session信息,这是就会出现问题.所以集群环境无法使用Session
2,有两种解决方案:
(1)服务器层面的
将session存储到公共服务器(Redis服务器).这时每个部署的web应用的服务器就会通过访问Redis服务器来查找session
<dependency> 
  <groupld>org.springframework.boot</groupld>
  <artifactld>spring-boot-starter-data-redis</artifactld></dependency>
<dependency>
  <groupld>org.springframework.session</groupld>
  <artifactld>spring-session-data-redis</artifactld>
</dependency>
 修改配置
spring.session.store-type=redis
server.servlet.session.timeout=1800
spring.session.redis.flush-mode=on_save
spring.session.redis.namespace=spring:session
spring.redis.host=82.157.14.10
spring.redis.password=
spring.redis.port=6379(2)客户端层面的(JWT令牌)
使用令牌技术
用户登录,将请求通过负载均衡传给一个服务器,服务器将密码和账户进行验证了之后生成一个令牌,并返回给客户端,客户端收到令牌之后可以储存到Cookie中或者储存到localStorage,当用户再次访问的时候,此时这台服务器会先进性验证是否有令牌,且令牌是否过期.
我们这里使用的是JWT令牌:
JSON Web Tokens - jwt.io(官方链接)

JWT分为三个部分:
header:包括令牌的类型,以及使用的哈希算法
payload(负载):存放一些有效信息,可以进行自定义
signature:此部分防止JWT内容被篡改,确保安全性
引入依赖:
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>生成签名:
签名有长度的限制,我们可以利用Keys.secretKeyFor()进行生成:
public void getKey(){
        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String encode = Encoders.BASE64.encode(secretKey.getEncoded());
        System.out.println(encode);
    }key是以HS256的编码生成,然后在通过Base64编码,将二进制形式的key编码成一个字符串,这就是签名
private static final String secret="4XhWaPIDMYSvaUTsqqDobe93QzC7gMCLd+xU5cIlNaA=";
private static final Key key=Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));上述代码第一行为getkey函数生成的签名,这个签名通过(相同编码:Base64)解码的方式再次转化成二进制的形式,然后通过Keys.hmacShaKeyFor()的方法生成key;
生成jwt令牌:
然后使用Jwts的构建器,构建一个令牌,其中可以设置以下相关参数:
| setId | 设置为一标识 | 
| setIssuer | 设置颁发者 | 
| setSubject | 设置主题 | 
| setIssuedAt | 设置颁发时间 | 
| setExpiration | 设置过期时间 | 
| signWith | 设置签名 | 
| setClaims | 自定义载荷 | 
| setAudience | 设置受众 | 
header中的哈希算法的值,可以通过识别 生成key的算法 来获取
 public static String getToken(Map<String,Object> map){
        return Jwts.builder()
                .setClaims(map)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis()+expirationTime))
                .signWith(key)
                .compact();
    }验证令牌是否合法:
利用Jwts生成一个解析器,在解析器中要对令牌的key(签名)进行配置,然后通过这个解析器验证令牌是否合法,代码如下:
public static Claims parseToken(String token){
        if (!StringUtils.hasLength(token)){
            throw new BlogException("token为空");
        }
        Claims claims = null;
        try {
            JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
            claims = build.parseClaimsJws(token).getBody();
            return claims;
        }catch (ExpiredJwtException e){
            log.error("token 过期");
        }catch (SignatureException e){
            log.error("签名不匹配");
        }catch (Exception e){
            log.error("token 解析失败,e:{}",e.getMessage());
        }
        return null;
    }(3)令牌实际运用
令牌技术通常与拦截器配合使用,用户访问某个网站的时候,我们通常要验证用户是否登录,就需要从客户端拿到token来验证是否登录,或者令牌是否过期
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截器进行拦截");
        String token=request.getHeader("user_header_token");
        String userId=request.getHeader("user_header_id");
        Claims claims = JwtUtil.parseToken(token);
        if (claims==null|| userId==null || !userId.equals(claims.get("id").toString())){
            response.setStatus(HttpStatus.UNAUTHORIZED.value());//返回401
            return false;
        }
        return true;
    }当一个用户登录的时候,我们就需要根据用户信息生成对应的token,然后储存到客户端(Cookie/localStorage.......)
public LoginResponse login(LoginParam loginParam) {
        UserInfo userInfo = selectOneUser(loginParam);
        if (userInfo==null){
            throw new BlogException("用户不存在");
        }
        if (!userInfo.getPassword().equals(loginParam.getPassword())){
            throw new BlogException("登录密码错误");
        }
        Map<String,Object> map=new HashMap<>();
        map.put("id", userInfo.getId());
        map.put("name",userInfo.getUserName());
        String token= JwtUtil.getToken(map);
        LoginResponse loginResponse=new LoginResponse(userInfo.getId(),token);
        BeanUtils.copyProperties(userInfo,loginResponse);
        return loginResponse;
    }二,加密加盐:
在数据库中,我们通常要对敏感信息比如,密码,身份证信息进行加密,以确保数据的安全性,加密的方式分为三种:
| 对称加密 | 常见的算法有:AES,DES,3DES... | 
| 非对称加密 | 常见的算法有RSA,DSA... | 
| 摘要算法 | 把任意长度的数据转化为固定长度输出数据的一种密码算法,摘要算法是不可逆的,也就是无法解密,常见算法有MD5,SHA系列... | 
这里我们以MD5算法举例:
由于MD5算法没有办法进行解密,经过MD5算法加密后的密文是一样的,所以如果用户的密码过于简单的话,即使通过MD5加密,也会很容易破解出来,所以我们采取密码拼接字符串的的方式,这个拼接的字符串就称之为"盐",首先加盐后的字符串本身就不易被破解,其次加盐后密码和盐混在一起,也增加了密码的安全性.
由此,我们数据库中的储存的是[(用户的密码+盐)一起进行MD5加密+盐].当用户登录的时候,我们取出数据库密码中的盐,然后将[(用户输入是密码+盐)MD5加密+盐],看这时生成是数据与数据库中的数据是否一致,如果一致,则说明密码正确,如果不一种,这密码错误.
注意:"(用户输入是密码+盐)MD5加密"中用户密码和盐的顺序可自定义,"[(用户输入是密码+盐)MD5加密+盐]"中,加密后的字符串加盐的顺序也可自定义.
进行加密:
public static String encrypt(String password){
        if(!StringUtils.hasLength(password)){
            throw new BlogException("密码不能为空");
        }
        String salt= UUID.randomUUID().toString().replace("-","");//盐
        String securityPassword=DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));
        return salt+securityPassword;
    }进行验证:
public static boolean verify(String password,String sqlSecurity){
        if (!StringUtils.hasLength(password)||!StringUtils.hasLength(sqlSecurity)){
            return false;
        }
        if (sqlSecurity.length()!=64){
            return false;
        }
        String salt = sqlSecurity.substring(0, 32);
        String securityPassword=DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));
        return (salt+securityPassword)
                .equals(sqlSecurity);
    }在程序应用中,只需要将从数据库中查询的过程改为上述两个方法就可以了.







![Buck开关电源闭环控制的仿真研究15V/5V[Matlab/simulink源码+Word文档]](https://i-blog.csdnimg.cn/direct/0277190a6b3549b38942ed7e09130916.png)











