SpringSecurity

SpringSecurity从入门到精通

简介

Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。

一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。

一般Web应用的需要进行认证授权

认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

授权:经过认证后判断当前用户是否有权限进行某个操作

而认证和授权也是SpringSecurity作为安全框架的核心功能。

1. 快速入门

1.1 准备工作

我们先要搭建一个简单的SpringBoot工程

① 添加依赖

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.5.0version>
parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <optional>trueoptional>
    dependency>
dependencies>

② 创建启动类

@SpringBootApplication
public class SecurityApplication {
   

    public static void main(String[] args) {
   
        SpringApplication.run(SecurityApplication.class,args);
    }
}

③ 创建Controller

@RestController
public class HelloController {
   

    @RequestMapping("/hello")
    public String hello(){
   
        return "hello";
    }
}

1.2 引入SpringSecurity

在SpringBoot项目中使用SpringSecurity我们只需要引入依赖即可实现入门案例。

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个SpringSecurity的默认登陆页面,默认用户名是user,密码会输出在控制台。

必须登陆之后才能对接口进行访问。

2. 认证

2.1 登陆校验流程

SpringSecurity_第1张图片

2.2 原理初探

2.2.1 SpringSecurity完整流程

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。
SpringSecurity_第2张图片

图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。

UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。

FilterSecurityInterceptor:负责权限校验的过滤器。

我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。
SpringSecurity_第3张图片

2.2.2 认证流程详解

SpringSecurity_第4张图片

概念速查:

Authentication接口:它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

2.3 解决问题

2.3.1 思路分析

登录

​ ①自定义登录接口

​ 调用ProviderManager的方法进行认证 如果认证通过生成jwt

​ 把用户信息存入redis中

​ ②自定义UserDetailsService

​ 在这个实现类中去查询数据库

校验:

​ ①定义Jwt认证过滤器

​ 获取token

​ 解析token获取其中的userid

​ 从redis中获取用户信息

​ 存入SecurityContextHolder

2.3.2 准备工作

①添加依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>fastjsonartifactId>
    <version>1.2.33version>
dependency>

<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwtartifactId>
    <version>0.9.0version>
dependency>

② 添加Redis相关配置

/**
 * Redis使用FastJson序列化
 * 
 * @author sg
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{
   

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
   
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJsonRedisSerializer(Class<T> clazz)
    {
   
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
   
        if (t == null)
        {
   
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
   
        if (bytes == null || bytes.length <= 0)
        {
   
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }

    protected JavaType getJavaType(Class<?> clazz)
    {
   
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}
@Configuration
public class RedisConfig {
   

    @Bean
    @SuppressWarnings(value = {
    "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
   
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

③ 响应类

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
   
    /**
     * 状态码
     */
    private Integer code;
    /**
     * 提示信息,如果有错误时,前端可以获取该字段进行提示
     */
    private String msg;
    /**
     * 查询到的结果数据,
     */
    private T data;

    public ResponseResult(Integer code, String msg) {
   
        this.code = code;
        this.msg = msg;
    }

    public ResponseResult(Integer code, T data) {
   
        this.code = code;
        this.data = data;
    }

    public Integer getCode() {
   
        return code;
    }

    public void setCode(Integer code) {
   
        this.code = code;
    }

    public String getMsg() {
   
        return msg;
    }

    public void setMsg(String msg) {
   
        this.msg = msg;
    }

    public T getData() {
   
        return data;
    }

    public void setData(T data) {
   
        this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
   
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

④工具类

/**
 * JWT工具类
 */
public class JwtUtil {
   

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    public static String getUUID(){
   
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
    
    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
   
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
   
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }
    
    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
   
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
   
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
   
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              	//唯一的ID
                .setSubject(subject)   		// 主题  可以是JSON数据
                .setIssuer("sg")     		// 签发者
                .setIssuedAt(now)      		// 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }


    public static void main(String[] args) throws Exception {
   
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
        Claims claims = parseJWT(token);
        System.out.println(claims);
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
   
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    
    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
   
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

}
@SuppressWarnings(value = {
    "unchecked", "rawtypes" }

你可能感兴趣的:(Spring,spring,架构)