SpringSecurity加密解密以及使用JWT鉴权

  • Post author:
  • Post category:其他




一、主要内容

1. SpringSecurity的加密解密
2. JWT鉴权
3. JWT的鉴权使用



二、SpringSecurity加密解密



1.pom依赖

  <!-- SpringSecurity 权限 -->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
  </dependency>



2.SpringSecurity配置类

因为SpringBoot已经集成了SpringSecurity,我们只要导入其依赖就会默认拦截全部请求.

需要使用配置类对其默认配置进行修改。

package lsh.config;

import org.springframework.context.annotation.Configuration;
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;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 10:45 上午
 * @desc :
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //authorizeRequests所有security全注解配置实现的开端,表示开始说明需要的权限。
        //需要的权限分两部分,第一部分是拦截的路径,第二部分访问该路径需要的权限。
        //antMatchers表示拦截什么路径,permitAll任何权限都可以访问,直接放行所有。
        //anyRequest()任何的请求,authenticated认证后才能访问
        //.and().csrf().disable();固定写法,表示使csrf拦截失效。
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}



3.Controller

@RestController
@RequestMapping("/admin")
public class AdminController {
    @Autowired
    AdminService service;
    @PostMapping("/addAdmin")
    public ResultObject addAdmin(@RequestBody Admin admin){
        service.addAdmin(admin);
        return new ResultObject(true, StatusCode.OK,"添加管理员成功",null);
    }

    @PostMapping(value = "/login")
    public ResultObject login(@RequestBody Admin admin){
        Admin adminLogin = service.login(admin);
        if(adminLogin==null){
            return new ResultObject(false, StatusCode.LOGINERROR, "登录失败");
        }
        return new ResultObject(true, StatusCode.OK, "登录成功");
    }
}



4.Service

public interface AdminService {
    void addAdmin(Admin admin);

    Admin login(Admin admin);
}






package lsh.service.Impl;

import com.lsh.util.IdWorker;
import lsh.model.Admin;
import lsh.repository.AdminRepository;
import lsh.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 1:43 下午
 * @desc :
 */
@Service
public class AdminServiceImpl implements AdminService {

    @Autowired
    AdminRepository adminRepository;
    @Autowired
    IdWorker idWorker;
    /**
     * 加解密
     */
    @Autowired
    private BCryptPasswordEncoder encoder;
    /**
     * 使用SpringSecurity的BCryptPasswordEncoder进行加密
     * @param admin
     */
    @Override
    public void addAdmin(Admin admin) {
        admin.setId( idWorker.nextId()+"" );
        //密码加密
        admin.setPassword(encoder.encode(admin.getPassword()));
        adminRepository.save(admin);
    }

    @Override
    public Admin login(Admin admin) {
        //先根据用户名查询对象。
        Admin adminLogin = adminRepository.findByLoginname(admin.getLoginname());
        //然后拿数据库中的密码和用户输入的密码匹配是否相同。
        if(adminLogin!=null && encoder.matches(admin.getPassword(), adminLogin.getPassword())){
            //保证数据库中的对象中的密码和用户输入的密码是一致的。登录成功
            return adminLogin;
        }
        //登录失败
        return null;

    }
}



5.Repository

public interface AdminRepository extends JpaRepository<Admin,String>, JpaSpecificationExecutor<Admin> {
    Admin findByLoginname(String loginname);
}



6.Model

package lsh.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 10:48 上午
 * @desc :
 */
@Data
@Entity
@Table(name="tb_admin")
public class Admin implements Serializable {

    /**
     * ID
     */
    @Id
    private String id;
    /**
     * 登陆名称
     */
    private String loginname;
    /**
     * 密码
     */
    private String password;
    /**
     * 状态
     */
    private String state;
}



三、JWT


JSON Web Token


JWT是一种在两方之间传输信息的方法。在JWT的主体中编码的信息被称为claims。JWT的扩展形式是JSON格式,因此每个claim都是JSON对象中的一个键。

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息



1.JWT的组成

一个JWT实际上就是一个字符串,它由三部分组成,

头部Header、载荷Payload、签名Signature。



头部

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。头部被表示成一个JSON对象。



载荷

载荷就是存放有效信息的地方:

比如登录的用户名,登录时间,登录过期时间,自定义数据(角色信息等等)

一个常见的载荷的写法



签名

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64加密后的)

payload (base64加密后的)

secret // 盐,也是一个秘钥

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。


注意:


secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

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

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoi5byg5LiJIiwiaWF0IjoxNjA0Mzg4ODM2LCJyb2xlcyI6ImFkbWluIiwicGVybWlzc2lvbiI6ImRlbGV0ZV9hbnl0aGluZyIsImV4cCI6MTYwNDQ3NTIzNn0.ptle3Y2065SljyNU6hNQuChEYUSwLaw29h_EDGu1zvw



登录生成token

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是

这样的:

  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向

    客户端返回请求的数据



以后每次访问的认证

客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);
认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;
如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;



2.Java的JJWT认证实现

JJWT是一个提供端到端的JWT创建和验证的Java库。

<dependencies>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
     <dependency>
            <groupId> org.springframework.boot </groupId>
            <artifactId> spring-boot-configuration-processor </artifactId>
            <optional> true </optional>
     </dependency>
</dependencies>



测试类代码

package com.lsh;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 2:27 下午
 * @desc :
 */
@SpringBootTest
public class JwtTest {
    public static final String key = "zhiyou";
    public static final Integer ttl = 10000;

    @Test
    public void test(){
        String jwt = createJWT("1", "张三", "admin", "delete_anything");
        System.out.println("jwt:"+jwt);
        Claims claims = parseJWT(jwt);
        System.out.println("claims:"+claims.toString());
    }

    public String createJWT(String id, String subject, String roles,String permission) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)//主题信息
                .setIssuer("LiuShihao")//颁发者
                .setIssuedAt(now)//办法时间
                .signWith(SignatureAlgorithm.HS256, key)//签名算法  盐
                //可以使用这种方式自定义载荷信息,也可以使用下面的方式
                // .claim("roles", roles)
                // .claim("permission",permission);
         //自定义载荷信息
         Map<String, Object> userinfo=new HashMap<string, Object>;
         userinfo.put(" company","集团号百");
         userinfo.put(" address","上海");
         userinfo.put(money", 3500);
         //添加载荷
         builder.addclaims(userinfo);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }
}

在这里插入图片描述



封装JwtUtil工具类

yml配置文件

jwt:
  config:
    key: zhiyou100
    # 有效时间24*60*60*1000
    ttl: 86400000
package com.lsh.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 2:16 下午
 * @desc :
 */
@Data
@Component
@ConfigurationProperties(prefix ="jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;

    /**
     * 生成JWT Token
     */
    public String createJWT(String id, String subject, String roles,String permission) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key)
                .claim("roles", roles)
                .claim("permission",permission);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * 解析JWT
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}

Token过期报错:

在这里插入图片描述



3.修改登陆逻辑

  1. 登录成功的
  2. 生成jwt令牌
  3. 把令牌信息存入到Cookie
  4. 把令牌作为参数传给用户

在这里插入图片描述

    /**
     * 登录成功的同时生成jwt令牌,令牌交给前端处理
     * @param admin
     * @return
     */
    @PostMapping(value = "/login")
    public ResultObject login(@RequestBody Admin admin){
        Admin adminLogin = service.login(admin);
        if(adminLogin==null){
            return new ResultObject(false, StatusCode.LOGINERROR, "登录失败");
        }
        String jwt = jwtUtil.createJWT(adminLogin.getId(), adminLogin.getLoginname(), "admin", "crud");
        Map<String, Object> map = new HashMap<>();
        map.put("token", jwt);
        map.put("admin",adminLogin);
        return new ResultObject(true, StatusCode.OK, "登录成功",map);
    }

在这里插入图片描述



4.操作鉴权

比如删除操作



Controller

  @DeleteMapping
    public ResultObject deleteById(String id){
        return service.deleteById(id);
    }



Service

ResultObject deleteById(String id);



ServiceImpl

    /**
     * 删除 必须有admin角色才能删除
     */
    @Override
    public ResultObject deleteById(String id) {
        String authorization = request.getHeader("Authorization");
        if (authorization == null) {
            return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,没有授权");
        } else {
            if (!authorization.startsWith("Bearer")) {
                return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,错误的Token");
            } else {
                //提取token
                String token = authorization.substring(7);
                Claims claims = null;
                try{
                     claims = jwtUtil.parseJWT(token);
                }catch (MalformedJwtException e){
                    return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,Token解析错误");
                }
                log.info("claims:"+claims);
                if (claims == null) {
                    return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,Token解析为空");
                } else {
                    if ((!"admin".equals(claims.get("roles")))) {
                        return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,不是管理员角色");
                    }else {
                        adminRepository.deleteById(id);
                        return new ResultObject(false, StatusCode.ACCESSERROR, "删除成功");
                    }
                }
            }
        }
    }



测试:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



四、使用拦截器实现token鉴权

使用SpringBoot提供的拦截器.在pre方法中对请求进行拦截.获取请求头,鉴权!

如果鉴权失败,抛出异常

鉴权成功,将角色信息放入请求域,后续操作从请求域获取数据即可!



1.自定义拦截器

package com.lsh.interceptor;

import com.lsh.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 4:50 下午
 * @desc :拦截器 对 Token 鉴权
 */
@Component
public class JwtInterceptor implements HandlerInterceptor {
    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("经过JwtInterceptor拦截器");
        //拦截器只是负责把头请求头中包含token的令牌进行一个解析验证。
        String header = request.getHeader("Authorization");

        if(header!=null && !"".equals(header)){
            //如果有包含有Authorization头信息,就对其进行解析
            if(header.startsWith("Bearer")){
                //得到token
                String token = header.substring(7);
                //对令牌进行验证
                try {
                    Claims claims = jwtUtil.parseJWT(token);
                    String roles = (String) claims.get("roles");
                    if(roles!=null && roles.equals("admin")){
                        request.setAttribute("claims_admin", token);
                    }
                    if(roles!=null && roles.equals("user")){
                        request.setAttribute("claims_user", token);
                    }
                }catch (Exception e){
                    throw new RuntimeException("令牌不正确!");
                }
            }
        }else {
            throw new RuntimeException("令牌为空!");
        }
        //无论如何都放行。具体能不能操作还是在具体的操作中去判断。
        return true;
    }
}



2.将自定义拦截器注册进Spring容器

package com.lsh.config;

import com.lsh.interceptor.JwtInterceptor;
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.WebMvcConfigurationSupport;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/3 4:52 下午
 * @desc :将JwtInterceptor注册到Spring容器
 */
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {

    @Autowired
    private JwtInterceptor jwtInterceptor;
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器要声明拦截器对象和要拦截的请求
        registry.addInterceptor(jwtInterceptor)
                //拦截所有请求 进入JwtInterceptor拦截器
                .addPathPatterns("/**")
                //排除登陆请求
                .excludePathPatterns("/**/login/**");
    }
}



版权声明:本文为DreamsArchitects原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。