Spring Boot + security + jwt 测试安全策略

一、测试概述

  主要目的是测试security的用法。
  因测试搭建mysql和redis比较麻烦,所以我这里将自定义的jwt和用户信息缓存到程序的内存中。
本人测试的项目比较混乱,Spring Boot父类只标出有用的依赖。其子类用的版本为jdk11。


后续会继续深入oauth2,敬请期待。
代码地址:https://gitcode.net/qq_40539437/cloud.git
如果想使用自定义jwt工具类往redis里存储请查看源码cloud-jwt模块。

整体代码结构:
在这里插入图片描述

二、maven 相关依赖

1、父类maven相关依赖

 <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <spring.boot.version>2.2.5.RELEASE</spring.boot.version>
        <spring.cloud.version>Hoxton.SR3</spring.cloud.version>
        <spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version> <!-- 版本丛2.1.0.RELEASE升到2.2.1.RELEASE 解决nacos 域名访问问题 -->
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>
 <dependencyManagement>
        <dependencies>
            <!--springboot 2.2.5-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--Spring cloud Hoxton.SR1-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--Spring cloud alibaba 2.1.0.RELEASE-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

2、子类相关依赖

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>10</source>
                    <target>10</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.2</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

二、代码块

1、配置文件 application.yaml

server:
  port: 20000

2、启动类

package com.atguigu.cloud;

import com.atguigu.cloud.cache.MyLocaleCache;
import com.atguigu.cloud.utils.RandomStr;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import java.util.Random;

@SpringBootApplication
@EnableWebSecurity
public class CsSecurityTestApplication {


    public static void main(String[] args) throws Exception {
        SpringApplication.run(CsSecurityTestApplication.class, args);
        key();
    }

    public static void key() throws Exception {
        Random random = new Random();
        int i = random.nextInt(60);
        String randomString = RandomStr.getRandomString(i);
        System.err.println("公 私钥随机串: " + randomString);

        // 生成密钥对
        RsaLocaleUtils.generateKey(MyLocaleCache.publicKey_S, MyLocaleCache.privateKey_S, randomString, 2048);
    }

}

3、本地缓存

package com.atguigu.cloud.cache;

import com.atguigu.cloud.domain.UserInfo;

import java.util.HashMap;
import java.util.Map;

public class MyLocaleCache {

    public static String privateKey_S = "privateKey";
    public static String publicKey_S = "publicKey";
    private static Map<Long, UserInfo> cache = new HashMap<>();

    public static UserInfo get(Long key){
        return cache.get(key);
    }

    public static UserInfo set(Long key, UserInfo userInfo){
        return cache.put(key,userInfo);
    }
}

4.配置类

package com.atguigu.cloud.conf;

import com.atguigu.cloud.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 密码加密方式 相同密码每次加密方式不同,但是每次都能与之匹配
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /** 配置Spring Security 的拦截规则*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login1").anonymous()
                .antMatchers("/test/**").hasRole("ADMIN") //需要具有 "ADMIN" 角色的用户访问 "/test/**"
                .anyRequest().authenticated(); // 其他路径需要认证


        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /** 权限管理器*/
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * {noop} 指代的是当前密码以明文输入  此处测试是指使用security自带的验证页面,设置密码,当实现UserDetailsService之后就不需要进行配置了
     * @param auth
     * @throws Exception

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("manager").password("{noop}manager").roles("manager")
                .and()
                .withUser("admin").password("{noop}admin").roles("manager", "ADMIN");
    }
     */
}

5.登录及权限验证接口

登录接口 LoginController:
这里不能直接使用login直接当接口,因为会找security里的login接口

package com.atguigu.cloud.controller;

import com.atguigu.cloud.cache.MyLocaleCache;
import com.atguigu.cloud.domain.LoginUser;
import com.atguigu.cloud.domain.UserInfo;
import com.atguigu.cloud.utils.JwtUtils;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/login1")
public class LoginController {

    @Autowired
    private AuthenticationManager authenticationManager;


    @GetMapping()
    public Map<String, Object> login(String username, String password) throws Exception {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username , password);
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        if(authentication == null) {
            throw new RuntimeException("用户名或密码错误");
        }
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        UserInfo userInfo = loginUser.getUser();
        MyLocaleCache.set(userInfo.getId(),userInfo);
        Map<String , Long> params = new HashMap<>() ;
        params.put("userId" , userInfo.getId()) ;
        PrivateKey privateKey = RsaLocaleUtils.getPrivateKey(MyLocaleCache.privateKey_S);
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 5);

        // 构建返回数据
        Map<String , Object> result = new HashMap<>();
        result.put("token" , token) ;

        System.out.println(username + "=" + password);
        return result;
    }
}

测试接口TestController:

package com.atguigu.cloud.controller;


import com.atguigu.cloud.domain.UserInfo;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/test")
public class TestController {

    @GetMapping("/hello")
    @ResponseBody
    public Map<String,Object> hello(){
        UserInfo userInfo = (UserInfo)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Map<String, Object> map = new HashMap<>();
        map.put("测试:","hello");
        map.put("用户:",userInfo.toString());
        return map;
    }
}

6.实体类

从写UserDetails:

package com.atguigu.cloud.domain;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/***
 * 下方其他认证可根据业务情况重写
 */
public class LoginUser implements UserDetails {
 
    private UserInfo userInfo;
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List list = new ArrayList();
        list.add(new SimpleGrantedAuthority("ROLE_"+ userInfo.getRole()));
        return list;
    }
 
    @Override
    public String getPassword() {
        return userInfo.getPassword();
    }
 
    @Override
    public String getUsername() {
        return userInfo.getUsername();
    }
 
    @Override
    public boolean isAccountNonExpired() {          // 账号是否没有过期
        return true;
    }
 
    @Override
    public boolean isAccountNonLocked() {           // 账号是否没有被锁定
        return true;
    }
 
    @Override
    public boolean isCredentialsNonExpired() {      // 账号的凭证是否没有过期
        return true;
    }
 
    @Override
    public boolean isEnabled() {                    // 账号是否可用
        return true;
    }

    public LoginUser() {
    }

    public LoginUser(UserInfo user) {
        this.userInfo = user;
    }

    public UserInfo getUser() {
        return userInfo;
    }

    public void setUser(UserInfo user) {
        this.userInfo = user;
    }
}

载荷Payload:

package com.atguigu.cloud.domain;

import java.util.Date;

public class Payload<T> {
    private String id;
    private T userInfo;
    private Date expiration;

    public Payload() {
    }

    public Payload(String id, T userInfo, Date expiration) {
        this.id = id;
        this.userInfo = userInfo;
        this.expiration = expiration;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public T getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(T userInfo) {
        this.userInfo = userInfo;
    }

    public Date getExpiration() {
        return expiration;
    }

    public void setExpiration(Date expiration) {
        this.expiration = expiration;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Payload{");
        sb.append("id='").append(id).append('\'');
        sb.append(", userInfo=").append(userInfo);
        sb.append(", expiration=").append(expiration);
        sb.append('}');
        return sb.toString();
    }
}

用户信息UserInfo:

package com.atguigu.cloud.domain;

public class UserInfo {

    private Long id;

    private String username;

    private String password;

    /** 这里多个可以采用分割符分割后续可以尽心解析*/
    private String role;

    public UserInfo() {
    }

    public UserInfo(Long id, String username, String role) {
        this.id = id;
        this.username = username;
        this.role = role;
    }

    public UserInfo(Long id, String username, String password, String role) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.role = role;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("UserInfo{");
        sb.append("id=").append(id);
        sb.append(", username='").append(username).append('\'');
        sb.append(", password='").append(password).append('\'');
        sb.append(", role='").append(role).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

7.JWT过滤器

package com.atguigu.cloud.filter;

import com.atguigu.cloud.cache.MyLocaleCache;
import com.atguigu.cloud.domain.Payload;
import com.atguigu.cloud.domain.UserInfo;
import com.atguigu.cloud.utils.JwtUtils;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
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.util.ArrayList;
import java.util.List;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 1、从请求头中获取token,如果请求头中不存在token,直接放行即可!由Spring Security的过滤器进行校验!
        String token = request.getHeader("token");
        if(token == null || "".equals(token)) {
            filterChain.doFilter(request , response);
            return ;
        }

        // 2、对token进行解析,取出其中的userId
        Payload<UserInfo> info = null;
        try {
            info = JwtUtils.getInfoFromToken(token, RsaLocaleUtils.getPublicKey("publicKey"), UserInfo.class);

        }catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法") ;
        }

        // 3、使用userId从redis中查询对应的LoginUser对象
        UserInfo userInfo1 = MyLocaleCache.get(info.getUserInfo().getId());
        if(userInfo1 != null) {
            List list = new ArrayList<>();
            list.add(new SimpleGrantedAuthority("ROLE_"+ userInfo1.getRole()));
            // 4、然后将查询到的LoginUser对象的相关信息封装到UsernamePasswordAuthenticationToken对象中,然后将该对象存储到Security的上下文对象中
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userInfo1, null ,list) ;
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }

        // 5、放行
        filterChain.doFilter(request , response);
    }
}

8.业务类

package com.atguigu.cloud.service;

import com.atguigu.cloud.domain.LoginUser;
import com.atguigu.cloud.domain.UserInfo;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    static Map<String, UserInfo> dataBase = new HashMap<>();

    static {
        /** 测试管理员用户*/
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1L);
        userInfo.setUsername("admin");
        userInfo.setPassword(new BCryptPasswordEncoder().encode("admin")); //不加密方式 "{noop}admin"
        userInfo.setRole("ADMIN");

        /** 测试普通用户*/
        UserInfo userInfo2 = new UserInfo();
        userInfo2.setId(2L);
        userInfo2.setUsername("manager");
        userInfo2.setPassword(new BCryptPasswordEncoder().encode("manager")); //不加密方式 "{noop}admin"
        userInfo2.setRole("MANAGER");


        dataBase.put("admin",userInfo);
        dataBase.put("manager",userInfo2);
    }
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        // 根据用户名查询用户数据
        UserInfo userInfo = dataBase.get(username);
        System.out.println(userInfo);
        // 如果查询不到数据,说明用户名或者密码错误,直接抛出异常
        if(userInfo == null) {
            throw new RuntimeException("用户名或者密码错误") ;
        }
 
        // 将查询到的对象转换成Spring Security所需要的UserDetails对象  这里不需要比对密码
        return new LoginUser(userInfo);
    }
}

9.工具类

JsonUtils

package com.atguigu.cloud.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;

public class JsonUtils {

    public static final ObjectMapper mapper = new ObjectMapper();

    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj.getClass() == String.class) {
            return (String) obj;
        }
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            logger.error("json序列化出错:" + obj, e);
            return null;
        }
    }

    public static <T> T toBean(String json, Class<T> tClass) {
        try {
            return mapper.readValue(json, tClass);
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }

    public static <E> List<E> toList(String json, Class<E> eClass) {
        try {
            return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }

    public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
        try {
            return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }

    public static <T> T nativeRead(String json, TypeReference<T> type) {
        try {
            return mapper.readValue(json, type);
        } catch (IOException e) {
            logger.error("json解析出错:" + json, e);
            return null;
        }
    }
}

JwtUtils

package com.atguigu.cloud.utils;

import com.atguigu.cloud.domain.Payload;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;


public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位分钟
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间,单位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 获取token中的载荷信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setExpiration(body.getExpiration());
        return claims;
    }


    public static void main(String[] args) {

    }
}

随机加密工具类:

package com.atguigu.cloud.utils;

import java.util.Random;

public class RandomStr {
    //length用户要求产生字符串的长度
    public static String getRandomString(int length) {
        String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(62);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }
}

RsaLocaleUtils

package com.atguigu.cloud.utils;

import org.springframework.stereotype.Component;

import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class RsaLocaleUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;

    private static ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();

    public static void setCache(String key,String value){
        cache.put(key, value);
    }

    public static String getCache(String key){
        return cache.get(key);
    }


    /**
     *  @Author: CS
     *  @Description: 从本地缓存中获取  可以修改为redis
     */
    public static void generateKey(String publicKey, String privateKey, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        setCache(publicKey,Base64.getEncoder().encodeToString(publicKeyBytes));
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        setCache(privateKey,Base64.getEncoder().encodeToString(privateKeyBytes));
    }


    /**
     * 从Redis中 读取密钥
     *
     * @param privateKey 私钥保存的Key
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
        privateKey = getCache(privateKey).toString();
        byte[] bytes  = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 从文件中 获取公钥
     *
     * @param publicKey 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(String publicKey) throws Exception {
        publicKey = getCache(publicKey).toString();
        byte[] bytes = Base64.getDecoder().decode(publicKey);
        System.out.println(bytes.length);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }
}

RsaUtils

package com.atguigu.cloud.utils;

import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

//import io.jsonwebtoken.security.Keys;

public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 从文件中 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {
        bytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }

    public static void testfor() {
        KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256);
        PrivateKey aPrivate = keyPair.getPrivate();
        PublicKey aPublic = keyPair.getPublic();
    }
}

10.测试类

package com.cloud.atguigu.jwt;

import com.atguigu.cloud.domain.Payload;
import com.atguigu.cloud.domain.UserInfo;
import com.atguigu.cloud.utils.JwtUtils;
import com.atguigu.cloud.utils.RandomStr;
import com.atguigu.cloud.utils.RsaLocaleUtils;
import org.junit.Test;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Random;


public class JwtTest {

    private  String privateKey_S = "privateKey";
    private String publicKey_S = "publicKey";
    @Test
    public void testRSAByLocale() throws Exception {

        Random random = new Random();
        int i = random.nextInt(60);
        String randomString = RandomStr.getRandomString(i);
        randomString = "cs";
        System.err.println(randomString);

        // 生成密钥对
        RsaLocaleUtils.generateKey(publicKey_S, privateKey_S, randomString, 2048);
        // 获取私钥
        PrivateKey privateKey = RsaLocaleUtils.getPrivateKey(privateKey_S);
        System.out.println("privateKey = " + privateKey.toString());
        PublicKey publicKey = RsaLocaleUtils.getPublicKey(publicKey_S);
        System.out.println("publicKey = " + publicKey.toString());
    }


    @Test
    public void testJWTByLocale() throws Exception {

        Random random = new Random();
        int i = random.nextInt(60);
        String randomString = RandomStr.getRandomString(i);
        randomString = "cs";
        System.err.println(randomString);

        // 生成密钥对
        RsaLocaleUtils.generateKey(publicKey_S, privateKey_S, randomString, 2048);
        // 获取私钥
        PrivateKey privateKey = RsaLocaleUtils.getPrivateKey(privateKey_S);
        // 生成token
        String token = JwtUtils.generateTokenExpireInMinutes(new UserInfo(1L, "Jack", "guest"), privateKey, 5);


        System.out.println("token = " + token);


        System.err.println("privateKey:" + privateKey.toString());


        // 获取公钥
        PublicKey publicKey = RsaLocaleUtils.getPublicKey(publicKey_S);
        // 解析token
        Payload<UserInfo> info = JwtUtils.getInfoFromToken(token, publicKey, UserInfo.class);

        System.out.println("info.getExpiration() = " + info.getExpiration());
        System.out.println("info.getUserInfo() = " + info.getUserInfo());
        System.out.println("info.getId() = " + info.getId());
    }
}

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于构建微服务的开源框架,它能够快速搭建项目并且提供了许多便捷的功能和特性。Spring Security 是一个用于处理认证和授权的框架,可以保护我们的应用程序免受恶意攻击。JWT(JSON Web Token)是一种用于身份验证的开放标准,可以被用于安全地传输信息。Spring MVC 是一个用于构建 Web 应用程序的框架,它能够处理 HTTP 请求和响应。MyBatis 是一个用于操作数据库的框架,可以简化数据库操作和提高效率。Redis 是一种高性能的键值存储系统,可以用于缓存与数据存储。 基于这些技术,可以搭建一个商城项目。Spring Boot 可以用于构建商城项目的后端服务,Spring Security 可以确保用户信息的安全性,JWT 可以用于用户的身份验证,Spring MVC 可以处理前端请求,MyBatis 可以操作数据库,Redis 可以用于缓存用户信息和商品信息。 商城项目的后端可以使用 Spring BootSpring Security 来搭建,通过 JWT 来处理用户的身份验证和授权。数据库操作可以使用 MyBatis 来简化与提高效率,同时可以利用 Redis 来缓存一些常用的数据和信息,提升系统的性能。前端请求则可以通过 Spring MVC 来处理,实现商城项目的整体功能。 综上所述,借助于 Spring BootSpring SecurityJWTSpring MVC、MyBatis 和 Redis 这些技术,可以构建出一个高性能、安全可靠的商城项目,为用户提供良好的购物体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值