当前位置:   article > 正文

spring boot3.x结合spring security最新版实现jwt登录验证_springboot3.x 整合springsecurity

springboot3.x 整合springsecurity

SpringSecurity

代码传送门
链接:https://pan.baidu.com/s/19ii5ffaweX0p52ptRGTD-w
提取码:m6j7

第一章 简介

SpringSecurity https://spring.io/projects/spring-security#overviewopen in new window

1、概念

Spring家族当中,一个安全管理框架。

Shiro也是一个安全框架,提供了很多安全功能。Shiro比较老,旧的项目当中,可能还在使用。上手还挺简单。

在新项目当中,一线互联网大型项目,都是使用SpringSecurity 。

2、认证 鉴权

一般的web项目当中,总会有登陆和鉴权的需求。但是大家一定要区分开。

  • 认证:验证当前访问的用户是不是本系统中的用户。确定是哪一个具体的用户。
  • 鉴权:经过认证,判断当前登陆用户有没有权限来执行某个操作。

所以说,安全框架SpringSecurity 当中,必定会有认证和鉴权的两大核心功能。

第二章 入门

1、准备web项目

(1)创建springboot web项目

快速构建

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.2</version>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
    <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>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

创建controller

@RestController
public class TestController {
    @GetMapping("test")
    public String test() {
        return "123Test";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

启动 测试 访问 :http://localhost:8099/test
在这里插入图片描述

2、引入SpringSecurity

(1)引入依赖
    <!-- 引入security起步依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
(2)测试

访问 : http://localhost:8099/test

Security 自带的登陆页面

在这里插入图片描述

可以输入自带默认用户名 user 和 密码(控制台)

Using generated security password: b6092291-ce28-4c5c-afc9-cb2e8c5fde06

就能访问到数据了

在这里插入图片描述

(3)自带退出

http://localhost:8099/logout

在这里插入图片描述

第三章 认证

1、web登陆流程

在这里插入图片描述

缺点可以改进:
  1. 现在使用的是security自带的登陆页面,比较丑。 想换成自己项目的,优化的登录页。
  2. 用户使用的是security给的用户名和密码。 想真实地去数据库里,获取真实的用户名和密码。
  3. security自带的cookie\session模式。 想自己生成jwt,无状态登陆。
  4. 前端页面怎么携带jwt。 想请求头里带上。
  5. 鉴权操作完全没有。 想鉴权做完善。

总而言之,自己的一些特定需求,都没有实现。

2、看源码

springsecurity 就是通过一些过滤器、拦截器,实现登陆鉴权的流程的。

(1)springsecurity 登陆流程

springsecurity就是一个过滤器链,内置了关于springsecurity的过滤器。

  • UsernamePasswordAuthenticationFilter:处理我们登陆页面输入的用户名和密码是否正确的过滤器。
  • ExceptionTranslationFilter:处理前面的几个过滤器中,有了问题,抛出错误,不让用户登录。
  • FilterSecurityInterceptor:经行一个权限校验的拦截器。

我们可以找到当前boot项目中的,所有有关security的过滤器链。

在这里插入图片描述

3、自定义登录

(1)思路

登陆: 1自定义登录接口

调用prodivermanager auth方法

登陆成功生成jwt

存入redis

2自定义userdetailsmanager实现类

从数据库中获取系统用户

访问资源:自定义认证过滤器

获取token

从token中获取userid

从redis中通过userid获取用户信息

存SecurityContextHolder

(2) JWT简介
a.概念

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

好处:不需要服务器端 存session。

特点:可以看到,但是不能篡改,因为第三部分用了秘钥。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。 abcd.abcd.abcd

头部(Header)

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

{"typ":"JWT","alg":"HS256"}
  • 1

在头部指明了签名算法是HS256算法。 我们进行BASE64编码[https://base64.us/

载荷(playload)

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

定义一个payload:

{"phone":"1234567890","login_user_key":"1"}
  • 1

签证(signature)

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

header

payload

secret

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

hs256("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iml0bGlscyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==",secret)
  • 1

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

JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMm5hbWUlMjIlM0ElMjJqYWNrJTIyJTJDJTIyYWRtaW4lMjIlM0F0cnVlJTdE.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 1
b. JWT签发与验证token

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

官方文档:

https://github.com/jwtk/jjwtopen in new window

c. 创建token
public String generateToken(String phone) {
        Calendar instance = Calendar.getInstance();
        // 设置过期时间
        instance.add(Calendar.SECOND, 1000);
        return Jwts.builder()
                .setSubject(phone)//主题
                .setIssuedAt(new Date(System.currentTimeMillis()))//签发日期
                .setExpiration(instance.getTime())// 设置过期时间
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
d.解析token

我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

public LoginUser getLoginUser(HttpServletRequest request) {
        String token = request.getHeader(header);
        if(Objects.isNull(token) || ObjectUtils.isEmpty(token)) {
            return null;
        }
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        // 解析对应的权限以及用户信息
        String username = claims.get("sub").toString();
        LoginUser user = redisCache.getCacheObject(CacheConstants.USER_INFO_KEY + username);
        System.out.println(user);
        return user;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

JWT工具类代码

@Component
@Slf4j
public class JwtUtilService {

    /**
     * 注入header值
     */
    @Value("${token.header}")
    private String header;

    /**
     * 注入secret密钥
     */
    @Value("${token.secret}")
    private String SECRET;


    public String generateToken(String phone) {
        Calendar instance = Calendar.getInstance();
        // 设置过期时间
        instance.add(Calendar.SECOND, 1000);
        return Jwts.builder()
                .setSubject(phone)//主题
                .setIssuedAt(new Date(System.currentTimeMillis()))//签发日期
                .setExpiration(instance.getTime())// 设置过期时间
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }

    public String getLoginUser(HttpServletRequest request) {
        String token = request.getHeader(header);
        if(Objects.isNull(token) || ObjectUtils.isEmpty(token)) {
            return null;
        }
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        // 解析对应的权限以及用户信息
        String username = claims.get("sub").toString();
        return username;
    }
    /**
     * 检查token是否过期
     *
     * @param  token token
     * @return boolean
     */
    public boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }
    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            log.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            log.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            log.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("JWT claims string is empty: {}", e.getMessage());
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

利用Spring注入机制

server:
  port: 8099
# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: Yx7GcP3UY194v8U.fLyhBiFZFxKOagQZt1baEhKlTfMW
  # 令牌有效期(默认30分钟)
  expireTime: 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

LoginUser类

@Data
public class LoginUser {
    //账号
    private String userName;
    //密码
    private String password;
    //验证码
    private Integer code;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:设置签名key必须和生成时一致。

(3) 准备新项目

①添加依赖

        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
		<!--JAXB API是java EE 的API,因此在java SE 9.0 中不再包含这个 Jar 包。-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

② 添加Redis相关配置

@Component
public class RedisCache {
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection) {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     *
     * @param key
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern) {
        return redisTemplate.keys(pattern);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
@Configuration
public class RedisConfig {

    /**
     * RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的一串很长的值
     * 缺点:可读性查、浪费存储空间
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//		1.创建 redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//		2.设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
//		3.设置序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//		key 和 hashkey 采用 String 序列化
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setKeySerializer(RedisSerializer.string());
//		value 和 hashvalue 采用 json 序列化
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        return  redisTemplate;
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

③ 响应类

package com.qf.common;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * @auther: sin
 * @Date: 2023/6/23 - 06 - 23 - 14:13
 * @Description: com.qf.common.pojo
 * @version: 1.0
 */
@Data
// 设置链式数据
@ToString
@Accessors(chain = true)
public class ResponseResult<T> {

    /**
     * 状态码
     */
    private int code;

    /**
     * 返回信息
     */
    private String message;

    /**
     * 返回数据
     */
    private T data;

    /**
     * 自定义返回成功数据
     * @param data
     * @return
     * @param <T>
     */
    public static <T> ResponseResult success(T data) {
        return new ResponseResult().setCode(200).setMessage("操作成功").setData(data);
    }

    /**
     * 自定义返回失败数据
     * @param data
     * @return
     * @param <T>
     */
    public static <T> ResponseResult fail(String message, T data) {
        return new ResponseResult().setCode(400).setMessage(message).setData(data);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

**重要!**实现真实从数据库获取系统用户信息

a. 数据库校验用户

创建UserDetailsService实现类,重写其中的方法。用户名从数据库中查询用户信息。

public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名去数据库查询用户信息
        SysUser user = new SysUser();

        //如果查询不到数据就通过抛出异常来给出提示
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名错误");
        }
        //TODO 根据用户查询权限信息 添加到LoginUser中

        //封装成UserDetails对象返回
        return new MyUserDetails(user);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
b.密码加密存储

1 实际项目中我们不会把密码明文存储在数据库中

2 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。

我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。

我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。

我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。

SecurityConfig配置

由于spring boot3.0废弃了extends WebSecurityConfigurerAdapter的方式,所以这里采用添加@Bean新方式

@Configuration
public class SecurityConfig{

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

密码加密:

1 12345 md5 --> asdfasdfasdfasdfa 默认密码12345

2 12345 md5(12345|itlils)---->uiowertupouert 加盐

3 BCryptPasswordEncoder 自动加盐

过时问题:

首先,过时也能用,如果看着不爽,可以使用如下方法。

以前我们自定义类继承自 WebSecurityConfigurerAdapter 来配置我们的 Spring Security,我们主要是配置两个东西:

  • configure(HttpSecurity)
  • configure(WebSecurity)

前者主要是配置 Spring Security 中的过滤器链,后者则主要是配置一些路径放行规则。

现在在 WebSecurityConfigurerAdapter 的注释中,人家已经把意思说的很明白了:

  • 以后如果想要配置过滤器链,可以通过自定义 SecurityFilterChainBean来实现。
  • 以后如果想要配置 WebSecurity,可以通过 WebSecurityCustomizerBean来实现。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    /**
     * 认证失败处理类
     */
    @Resource
    private AuthenticationEntryPointImpl unauthorizedHandler;

    @Resource
    private AccessDeniedHandlerImpl accessDeniedHandler;

    /**
     * 登出处理
     */
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     *
     */
    @Resource
    private AuthenticationConfiguration authenticationConfiguration;

    /**
     * token拦截器
     */
    @Resource
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler)
                .and()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .requestMatchers("/login").anonymous()
                // 无论是否登录都可以访问
               .requestMatchers("/captchaImage").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .requestMatchers().authenticated();

        //把token校验过滤器添加到过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加Logout filter
        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 允许跨域
        http.cors();

        return http.build();
    }



    @Bean
    public AuthenticationManager authenticationManager() throws Exception{
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        return authenticationManager;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
c.自定义登陆接口

分析需求:

1 自定义一个controller登陆接口

@RestController
public class LoginController {

    @Autowired
    private LoginService loginService;

    @RequestMapping("/login")
    public ResponseResult Login(@RequestBody LoginUser user, HttpServletResponse response) {
        return loginService.login(response, user.getUserName(), user.getPassword());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2 放行自定义登陆接口

// 对于登录接口 允许匿名访问
.requestMatchers("/login").anonymous()
  • 1
  • 2

3使用ProviderManager auth方法进行验证

4自己生成jwt给前端

5系统用户相关所有信息放入redis

d.认证过滤器

1.获取token

2.解析token

3.获取userid

4.封装Authentication

5.存入SecurityContextHolder

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    JwtUtilService jwtUtil;

    @Autowired
    RedisCache redisCache;

    @Override
    public ResponseResult login(HttpServletResponse response, String username, String password) {
        // 1登录前置校验
//        PreCheck.loginPreCheck(username, password);
        // 2验证码校验
//        PreCheck.validateCaptcha(code, uuid, redisCache);
        // 3使用ProviderManager auth方法进行验证
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        // 校验失败了
        if (Objects.isNull(authenticate)) {
            return new ResponseResult<>().setCode(HttpStatus.ERROR).setMessage("用户名或密码错误!");
        }
        MyUserDetails userDetails = (MyUserDetails) (authenticate.getPrincipal());

        // 4自己生成jwt token给前端
        String token = jwtUtil.generateToken(username);

        // 5系统用户token放入redis
        //redisCache.setCacheObject(CacheConstants.LOGIN_TOKEN_KEY + username, token, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
        // 6系统用户信息放入redis
        //redisCache.setCacheObject(CacheConstants.USER_INFO_KEY + username, userDetails, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
        System.out.println(userDetails);
        //System.out.println(loginUser.getUser());
        // 根据用户id获取所有的角色信息
//        List<SysUserRole> roleList = sysUserRoleService.RoleIdList(loginUser.getUser().getUserId());


        Map<String, Object> map = new HashMap();
        map.put("token", token);
        map.put("userInfo", userDetails.getUser());
        return new ResponseResult<>().setCode(HttpStatus.SUCCESS).setMessage(Constants.SUCCESS).setData(map);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {


    @Autowired
    RedisCache redisCache;

    @Autowired
    JwtUtilService jwtUtilService;
    //
    //@Autowired
    //SysUserService sysUserService;

    //@Autowired
    //UserDetailsServiceImpl userDetailsService;

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

        //1解析token
        MyUserDetails userDetails = jwtUtilService.getLoginUser(request);
        if(!Objects.isNull(userDetails)) {
            //2封装Authentication
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                    = new UsernamePasswordAuthenticationToken(userDetails, null, null);
            System.out.println(usernamePasswordAuthenticationToken);
            //5存入SecurityContextHolder
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        }
        //放行,让后面的过滤器执行
        filterChain.doFilter(request, response);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

测试

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/388672
推荐阅读
相关标签
  

闽ICP备14008679号