SpringSecurity 认证实现

news2024/12/22 23:35:51

在之前一篇 博客 已经说明了 SpringSecurity 认证与授权的原理。这篇用来具体实现一下。

1、新建SecurityConfig 并创建认证管理器

@Bean
public AuthenticationManager authenticationManager() {
	...
}

2、新建认证提供者

@Configuration
public class SystemUserPasswordAuthenticationProvider extends DaoAuthenticationProvider {
	...
}

3、将认证提供者加入认证管理器

@Resource
private SystemUserPasswordAuthenticationProvider systemUserPasswordAuthenticationProvider;

@Bean
public AuthenticationManager authenticationManager() {
    // 将认证提供者 放入认证管理器
    return new ProviderManager(systemUserPasswordAuthenticationProvider);
}

4、书写LoginServiceImpl,注入AuthenticationManager,执行其认证方法

@Service
public class LoginServiceImpl implements LoginService {

    // 拿到认证管理器
    @Resource
    AuthenticationManager authenticationManager;

	// dto 是用户名和密码 
    @Override
    public LoginResponseVO login(UserDTO dto) {
        Authentication authenticate = authenticationManager.authenticate(new SystemUserPasswordAuthenticationToken(dto));
        ....
    }

5、调用认证管理器的authenticate,在源码中发现,调用此方法时需要传入一个 Authentication 类型的参数,用于匹配认证提供者。同时认证提供者还需实现 suppoet方法绑定到它的 Authentication。
因此为 SystemUserPasswordAuthenticationProvider 创建一个对应的 SystemUserPasswordAuthenticationToken,其继承自 UsernamePasswordAuthenticationToken

public class SystemUserPasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
	// 必须在子类的构造方法中调用父类的构造方法,用来对父类的一些变量进行初始化赋值
	// 这里是对父类的 principal 和 credentials 赋值
    public SystemUserPasswordAuthenticationToken(UserDTO dto) {
        super(dto.getUsername(), dto.getPassword());
    }
}

其次在 SystemUserPasswordAuthenticationProvider 中实现 supports 方法

@Override
public boolean supports(Class<?> authentication) {
    // A.isAssignableFrom(B) 判断 一个类(B)是不是继承来自于另一个父类(A);一个接口(A)是不是实现了另外一个接口(B);或者两个类相同;
    return (SystemUserPasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

继续看源码,会发现通过传入的 Authentication ,遍历 AuthenticationManager 中的所有 AuthenticationProvider 中的 supports 方法,匹配到 SystemUserPasswordAuthenticationProvider 后 调用了其 authenticate 方法。该方法默认会调用父类 AbstractUserDetailsAuthenticationProvider 的 authenticate 方法。 可以自己实现 authenticate 方法来自定义一些规则,最后再执行super去执行父类的authenticate方法。但如果只是做用户名和密码的认证的话可以不实现。

比如,有时可能有ca认证、即传入的dto不仅仅是账号密码,这时候,new SystemUserPasswordAuthenticationToken 时会将 dto 的整体赋值给 SystemUserPasswordAuthenticationToken 父类的 principal,而 credentials 为null。直到调用到我们重写的 authenticate 时,将dto中包含的多余的信息做处理,然后将 账号密码取出来 封装一个 UsernamePasswordAuthenticationToken,然后再调用 父类的 authenticate 方法。 如下图所示:

在这里插入图片描述
重写的 authenticate 方法,来处理 dto 中 ca信息,ca登录成功的话就不往后认证了
在这里插入图片描述

6、我采取只接受账号和密码,因此未重写 authenticate。继续跟着源码走,执行父类 的 authenticate 方法中,会执行 retrieveUser(username, authentiacation) 方法,进而会执行一个 loadUserByUsername(username) 方法,这个方法是用来在数据库根据用户名查找到用户信息。因此需要我们实现。分析源码发现,每个认证提供者的父类都包含一个 UserDetailsService 类型的属性,它是一个接口,实现了 loadUserByUsername 方法。因此我们需要创建一个类SystemUserDetailServiceImpl 实现 UserDetailsService 接口,并重写 loadUserByUsername 方法,然后添加到 SystemUserPasswordAuthenticationProvider 中。由于 loadUserByUsername 返回类型为 UserDetails,因此还需创建一个类 SystemUserDetail 实现 UserDetails 接口

SystemUserDetailServiceImpl :

@Service(value = "systemUserDetailService")
public class SystemUserDetailServiceImpl implements UserDetailsService {
    @Resource
    private SysUserMapper sysUserMapper;

    // 由于返回的是一个 UserDetails 对象,因此还需新建一个继承自 UserDetails 的类
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, username));
        Optional.ofNullable(user).orElseThrow(() -> new BadCredentialsException("用户名或密码错误"));
        return new SystemUserDetail(user, user.getPassword());
    }
}

SystemUserDetail:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SystemUserDetail implements UserDetails {

    // 自定义,封装个SysUser对象 和 密码
    private SysUser sysUser;
    private String password;

    // 实现 UserDetails 接口,必须实现下面这些方法。
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return sysUser.getUserName();
    }
    // 这些方法是用来检查查询到的 User 对象的,都设为 true。
    @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 static SysUser getCurrentSystemUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication.getPrincipal().equals("anonymous")) {
            throw new BadCredentialsException("当前为匿名登陆,无法访问");
        }
        // 在源码中分析到,最后返回的Authentication 其 principal 属性返回的是一个 UserDetails对象,我们直接可返回 SystemUserDetail
        SystemUserDetail principal = (SystemUserDetail) authentication.getPrincipal();
        SysUser systemUser = principal.getSysUser();
        if (ObjectUtils.isNull(systemUser)) {
            throw new BadCredentialsException("登陆状态失效,请重新登陆");
        }
        return systemUser;
    }
}

将 SystemUserPasswordAuthenticationProvider 与 SystemUserDetailServiceImpl 绑定在一起:
UserDetailService 作为认证提供者父类的一个属性。可直接在 SystemUserPasswordAuthenticationProvider 的构造方法中设置,这样的话当SecurityConfig中注入 SystemUserPasswordAuthenticationProvider 时,会自动创建实例,绑定到 SystemUserDetailServiceImpl。

public SystemUserPasswordAuthenticationProvider(@Qualifier("systemUserDetailService") UserDetailsService userDetailsService) {
    setUserDetailsService(userDetailsService);
}

7、继续看源码发现,获取到数据库中用户信息后,接下来 会调用check方法去检查用户,也就是调用 UserDetails 中那些必须重写的方法。检查完之后会调用 additionalAuthenticationChecks(userDetails, authentication)
userDetails 是我们调用自己重写的 loadUserByUsername方法返回的重写的 SystemUserDetail 对象,也就是从数据库中查询到的数据,authentication是包含我们dto传递的用户名和密码。这个方法就是来检查密码是否匹配的。我们可以进行重写来自己检查。因此在 SystemUserPasswordAuthenticationProvider 中实现此方法

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

    if(!StringUtils.equals(userDetails.getPassword(),(String) authentication.getCredentials())) {
        throw new BadCredentialsException("认证失败,账号或密码错误");
    } else {
        logger.info("登陆成功");
    }
}

8、继续看源码的话最后会发现,认证成功后会执行 createSuccessAuthentication 方法,并最终返回一个 UsernamePasswordAuthenticationToken 对象,其 principal 是一个 UserDetails 类型,在我这里就是 SystemUserDetail 对象,其 credentials 就是用户的密码。这个方法不需要实现也可以。

9、认证完成之后,将认证信息存入 security 上下文

SecurityContextHolder.getContext().setAuthentication(authenticate);
// 获取当前登陆的user对象
SysUser currentSystemUser = SystemUserDetail.getCurrentSystemUser();

10、将当前登录的用户的对象存入redis,并设置过期时间,然后签发token。
存入redis的形式是:

cacheKey : SysUser
uuid : username
public LoginResponseVO login(UserDTO dto) {
    /*
       调用认证管理器中的认证方法,此认证方法会根据传入的参数,匹配到认证管理器中的某一认证提供者,然后调用认证提供者的authenticate方法
       传入的参数是一个 UsernamePasswordAuthenticationToken 类型,包含 principle credentials 两个参数 可以理解为 账户和密码
       传入的 UsernamePasswordAuthenticationToken 这个类型主要用于匹配认证提供者。每个认证提供者都与一个特定的 UsernamePasswordAuthenticationToken 类型绑定。
       认证管理器调用的authenticate方法中会调用每个认证提供者的supports方法,在这个方法中会判断认证提供者绑定的 UsernamePasswordAuthenticationToken类型 与传入的 UsernamePasswordAuthenticationToken类型是否匹配,如果匹配,就找到了此次登录的认证提供者!
    */
    Authentication authenticate = authenticationManager.authenticate(new SystemUserPasswordAuthenticationToken(dto));
    // 将认证信息存入 security 上下文
    SecurityContextHolder.getContext().setAuthentication(authenticate);
    // 获取当前登陆的user对象
    SysUser currentSystemUser = SystemUserDetail.getCurrentSystemUser();
    // 缓存中的key: "TOKEN:" + username + snowid。这个key会写入到签发的token中,以便于后续由token就可以在缓存中找到用户对象
    String cacheKeyToken = getCacheKeyToken(currentSystemUser.getUserName(), String.valueOf(IdGeneratorUtil.snowflakeId()));
    // 将当前登陆的用户对象存入redis,并设置过期时间
    RedisUtil.getRedisService().set(cacheKeyToken, currentSystemUser, JwtTokenUtils.EXPIRE_TIME);
    // 生成临时校验code,key 格式为"AUTH:CODE:" + uuid,username作为value,存入redis
    String uuid = IdGeneratorUtil.simpleUUID();
    RedisUtil.getRedisService().set(formatTempAuthCode(uuid), currentSystemUser.getUserName(), JwtTokenUtils.EXPIRE_TIME);
    // 现在是根据User对象的用户名、缓存中的key,签发token
    return new LoginResponseVO(JwtTokenUtils.generateManageToken(currentSystemUser, cacheKeyToken), uuid);
}


// 获取用户信息 在缓存中的 key
public static String getCacheKeyToken(String userName, String uuid) {
    String cacheKey = "TOKEN:%s:%s";
    return String.format(cacheKey, userName, uuid);
}

// 获取临时授权码 在缓存中的 key 
public static String formatTempAuthCode(String snowid) {
    String cacheKey = "AUTH:CODE:%s";
    return String.format(cacheKey, snowid);
}

这里引入了工具类 JwtTokenUtils,这个类封装了一些与Token相关的方法:

  • 由给出的用户信息和缓存key信息生成token
  • 解析Token为Claims对象
  • 从请求中获取token

还有一些属性字段:

  • token名称
  • 密钥
  • 过期时间
public class JwtTokenUtils implements Serializable {
    private static final long serialVersionUID = 1L;

	// request中token的名字
    private static final String TOKEN_NAME = "token";
    /**
     * 密钥,生成token是用密钥加密,解析数据时用密钥解密
     */
    private static final String SECRET = "lmhlmh";
    /**
     * 过期时间,用户信息放入redis的过期时间。也可以理解为是token过期时间,因为当redis中存的用户信息过期了,token验证也通不过了,需要用户重新登陆!
     */
    public static final long EXPIRE_TIME = 24 * 60 * 60;

	// 下面这两个方法用于生成token
    public static String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, SECRET).compact();
    }
	// 将想存的信息存入token,必须包含的是 username 和 cacheKey
	// username 用于直接比对上下文登录对象
	// cacheKey 用于去缓存中拿对象,并判断登录是否已过期
    public static String generateManageToken(SysUser systemUser, String cacheKey) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", systemUser.getUserName());
        claims.put("email", systemUser.getEmail());
        claims.put("phonenumber", systemUser.getPhonenumber());
        claims.put("sex", systemUser.getSex());
        claims.put("cacheKey", cacheKey);
        String token = JwtTokenUtils.generateToken(claims);
        return token;
    }


    public static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            // 在签发token的时候 是用 cacheKey 和 username,这里就是拿到这两个键值
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
            System.out.println(claims); // {cacheKey=TOKEN:lmh:1601582677075558400, username=lmh}
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    public static String getTokenFromRequest(HttpServletRequest request) {
        // 从请求头中拿取token
        String token = request.getHeader(TOKEN_NAME);
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }
}

生成雪花id 和 uuid 时引入了 IdGeneratorUtil工具类:

@Component
public class IdGeneratorUtil {
    private long workerId = 0;
    private static Snowflake snowflake;

    @PostConstruct
    void init() {
        snowflake = IdUtil.createSnowflake(workerId, 1);
    }

    /**
     * 获取一个批次号,形如 2019071015301361000101237
     * <p>
     * 数据库使用 char(25) 存储
     *
     * @return 返回批次号
     */
    public synchronized static String batchId(String prefix) {
        String date = DateTime.now().toString(DatePattern.PURE_DATETIME_PATTERN);
        return prefix + date;
    }


    /**
     * 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42
     *
     * @return
     */
    public static String simpleUUID() {
        return IdUtil.simpleUUID();
    }

    /**
     * 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3
     *
     * @return
     */
    public static String randomUUID() {
        return IdUtil.randomUUID();
    }


    public static synchronized long snowflakeId() {
        return snowflake.nextId();
    }

    public synchronized long snowflakeId(long workerId, long dataCenterId) {
        Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
        return snowflake.nextId();
    }

}

11、这里说一下 Redis 的配置:
(1)增添 redis 依赖

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)application.xml 文件
配置 redis 连接信息。

spring:
  redis:
    port: 6379
    database: 0 # 指定数据库编号 
    host: master
    timeout: 10000
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

(3)RedisConfig 文件
自定义 Redistemplate,并装配到Spring容器中管理。
这里一般是固定的模板:

@EnableCaching
@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
public class RedisConfig {

    @Bean(value = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用 GenericFastJsonRedisSerializer 替换默认序列化
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        // 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new GenericToStringSerializer<>(Object.class));
        redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);
        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new GenericToStringSerializer<>(Object.class));
        redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);
        // 设置支持事物
        //redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

(4)编写RedisService接口和实现类:
实现类中注入自定义的 RedisTemplate,实现redis中常用的各种方法,然后将实现类交给 Spring 容器中。

public interface RedisService {
    /**
     * 保存属性
     */
    void set(String key, Object value, long time);

    void set(String key, Object value, long time, TimeUnit timeUnit);

    /**
     * redis 原子类自增
     *
     * @param key      key
     * @param time     时间
     * @param timeUnit 单位
     * @return 自增后的值
     */
    long increment(String key, long time, TimeUnit timeUnit);

    /**
     * 保存属性
     */
    void set(String key, Object value);

    /**
     * 获取属性
     */
    Object get(String key);

    /**
     * 删除属性
     */
    Boolean del(String key);

    /**
     * 批量删除属性
     */
    Long del(List<String> keys);

    /**
     * 设置过期时间
     */
    Boolean expire(String key, long time);

    /**
     * 设置过期时间
     */
    Boolean expire(String key, long time, TimeUnit timeUnit);

    /**
     * 获取过期时间
     */
    Long getExpire(String key);

    /**
     * 判断是否有该属性
     */
    Boolean hasKey(String key);

    /**
     * 按delta递增
     */
    Long incr(String key, long delta);

    /**
     * 按delta递减
     */
    Long decr(String key, long delta);

    /**
     * 获取Hash结构中的属性
     */
    Object hGet(String key, String hashKey);

    /**
     * 向Hash结构中放入一个属性
     */
    Boolean hSet(String key, String hashKey, Object value, long time);

    /**
     * 向Hash结构中放入一个属性
     */
    void hSet(String key, String hashKey, Object value);

    /**
     * 直接获取整个Hash结构
     */
    Map<Object, Object> hGetAll(String key);

    /**
     * 直接设置整个Hash结构
     */
    Boolean hSetAll(String key, Map<String, Object> map, long time);

    /**
     * 直接设置整个Hash结构
     */
    void hSetAll(String key, Map<String, Object> map);

    /**
     * 删除Hash结构中的属性
     */
    void hDel(String key, Object... hashKey);

    /**
     * 判断Hash结构中是否有该属性
     */
    Boolean hHasKey(String key, String hashKey);

    /**
     * Hash结构中属性递增
     */
    Long hIncr(String key, String hashKey, Long delta);

    /**
     * Hash结构中属性递减
     */
    Long hDecr(String key, String hashKey, Long delta);

    /**
     * 获取Set结构
     */
    Set<Object> sMembers(String key);

    /**
     * 向Set结构中添加属性
     */
    Long sAdd(String key, Object... values);

    /**
     * 向Set结构中添加属性
     */
    Long sAdd(String key, long time, Object... values);

    /**
     * 是否为Set中的属性
     */
    Boolean sIsMember(String key, Object value);

    /**
     * 获取Set结构的长度
     */
    Long sSize(String key);

    /**
     * 删除Set结构中的属性
     */
    Long sRemove(String key, Object... values);

    /**
     * 获取List结构中的属性
     */
    List<Object> lRange(String key, long start, long end);

    /**
     * 获取List结构的长度
     */
    Long lSize(String key);

    /**
     * 根据索引获取List中的属性
     */
    Object lIndex(String key, long index);

    /**
     * 向List结构中添加属性
     */
    Long lPush(String key, Object value);

    /**
     * 向List结构中添加属性
     */
    Long lPush(String key, Object value, long time);

    /**
     * 向List结构中批量添加属性
     */
    Long lPushAll(String key, Object... values);

    /**
     * 向List结构中批量添加属性
     */
    Long lPushAll(String key, Long time, Object... values);

    /**
     * 从List结构中移除属性
     */
    Long lRemove(String key, long count, Object value);
}
@Service("redisService")
public class RedisServiceImpl implements RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void set(String key, Object value, long time) {
        set(key, value, time, TimeUnit.SECONDS);
    }

    @Override
    public void set(String key, Object value, long time, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, time, timeUnit);
    }

    @Override
    public long increment(String key, long time, TimeUnit timeUnit) {
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        redisAtomicLong.expire(time, timeUnit);
        return redisAtomicLong.incrementAndGet();
    }

    @Override
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public Boolean del(String key) {
        return redisTemplate.delete(key);
    }

    @Override
    public Long del(List<String> keys) {
        return redisTemplate.delete(keys);
    }

    @Override
    public Boolean expire(String key, long time) {
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    @Override
    public Boolean expire(String key, long time, TimeUnit timeUnit) {
        return redisTemplate.expire(key, time, timeUnit);
    }

    @Override
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    @Override
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    @Override
    public Long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    @Override
    public Long decr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    @Override
    public Object hGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    @Override
    public Boolean hSet(String key, String hashKey, Object value, long time) {
        redisTemplate.opsForHash().put(key, hashKey, value);
        return expire(key, time);
    }

    @Override
    public void hSet(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    @Override
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public Boolean hSetAll(String key, Map<String, Object> map, long time) {
        redisTemplate.opsForHash().putAll(key, map);
        return expire(key, time);
    }

    @Override
    public void hSetAll(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    @Override
    public void hDel(String key, Object... hashKey) {
        redisTemplate.opsForHash().delete(key, hashKey);
    }

    @Override
    public Boolean hHasKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    @Override
    public Long hIncr(String key, String hashKey, Long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    @Override
    public Long hDecr(String key, String hashKey, Long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, -delta);
    }

    @Override
    public Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    @Override
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    @Override
    public Long sAdd(String key, long time, Object... values) {
        Long count = redisTemplate.opsForSet().add(key, values);
        expire(key, time);
        return count;
    }

    @Override
    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    @Override
    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    @Override
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    @Override
    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    @Override
    public Long lSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    @Override
    public Object lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    @Override
    public Long lPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    @Override
    public Long lPush(String key, Object value, long time) {
        Long index = redisTemplate.opsForList().rightPush(key, value);
        expire(key, time);
        return index;
    }

    @Override
    public Long lPushAll(String key, Object... values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    @Override
    public Long lPushAll(String key, Long time, Object... values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        expire(key, time);
        return count;
    }

    @Override
    public Long lRemove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }
}

(5)其实此时已经可以用了,哪里用的话,直接注入 redisService 就可以了。
但再封装一个 RedisUtils 用来拿到容器中的 redisService,这样的话在别处用的时候直接 RedisUtil.getRedisService(). 就可以了。

@Component
public class RedisUtil {

    public static RedisService getRedisService() {
        return Inner.REDIS_SERVICE;
    }

    private static class Inner {
        private static final RedisService REDIS_SERVICE = (RedisService) MyProjectApplication.ac.getBean("redisService");
    }
}
/*
这里的 MyProjectApplication 是我的启动类,ac是我在启动类里创建的一个实例,
用来获取到ApplicationContext对象,从而再调用getBean方法。
*/

注意: 这里是在静态类里通过 MyProjectApplication.ac.getBean 来拿到Spring容器中的 redisService 对象。这里不可以采取注入的方式,因为 getRedisService 必须是一个静态方法来让外部去调用,而通过 @Resource 或 @Autowired 注入的对象不能是静态的,因为在项目启动时,会加载所有带有类似@Component等注解的类,并加载该类下的所有静态属性。也就是说,如果 @Autowired static redisService 这种形式的话,会报空指针错误,也就是会拿不到,因为可能redisService类还没加载呢,这里的静态属性先加载了。但是在类加载时,它里面的静态内部类不会随着加载,而是会在第一次被调用时加载,因此将获取bean的方法放在内部类里,就可以保证在getBean时,redisService已经被加载完毕了(redisService是另一个类装配到Spring容器中的)。

还需注意: 在 java中,若想在另一个类直接通过类名访问到本类的方法,方法必须是静态的。并且静态方法中不能包括非静态字段。如果返回某个字段时,该字段有也必须是静态的。

12、回到 SecurityConfig,开始配置对请求的身份验证。

编写一个 securityFilterChain 方法,接收 HttpSecurity 参数,返回 SecurityFilterChain 然后装配到 Spring 容器。

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
    // 放行的路径,即这些请求不需要验证
    for (String url : ignoreUrlConfig.getIgnoreUrlList()) {
        registry.antMatchers(url).permitAll();
    }
    return httpSecurity.cors().and().csrf().disable()
            .authorizeRequests() // 请求授权
            .anyRequest() // 任何请求
            .authenticated() // 都必须经过身份验证
            .and()
            // 自定义权限拦截器 JWT过滤器
            .addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling().authenticationEntryPoint(noAuthenticationEntryPoint)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .headers().cacheControl()
            .and()
            .and()
            .build();
}

针对上面用到的
(1)放行路径

在 application.yml 中指明放行路径

secure:
  ignoreUrlList:
    - /login/**
    - /doc.html
    - /swagger-resources/**
    - /v3/api-docs
    - /swagger*/**
    - /webjars/**

新建一个类用来拿到yml文件中的配置,然后装配到Spring容器。

@Data
@Configuration
@ConfigurationProperties(prefix = "secure")
public class IgnoreUrlConfig {

    private List<String> ignoreUrlList;

}

然后在SecurityConfig中注入进来,并设置这些url permitAll();

@Resource
private IgnoreUrlConfig ignoreUrlConfig;

ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
// 放行的路径
for (String url : ignoreUrlConfig.getIgnoreUrlList()) {
    registry.antMatchers(url).permitAll();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80926.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle项目管理之PrimaveraUnifier组织-业主/合作伙伴公司

目录 一、业主公司 二、合作伙伴公司 三、成员公司 Oracle Primavera Unifier 是企业项目协同管理系统&#xff0c;在国际化项目管理中&#xff0c;在进行常规的业务管理之外&#xff0c;对合同公司/EPC或分包供应商也有一定的管理要求&#xff0c;在Unifier中为了更好的实现…

sja1000 CAN驱动学习、调试记录(基于PeliCan Mode)

一、基础知识 网上讲sja1000 CAN总线控制器的资料很多&#xff0c;这里放一个引路贴&#xff1a;(151条消息) CAN总线控制器SJA1000_FATE的博客-CSDN博客_sja1000 BasicCAN Mode&#xff1a;仅支持11位的ID。 PeliCan Mode&#xff1a;在扩展模式下&#xff0c;允许使用 11 位 …

找出DataFrame中指定数据类型的列:select_dtypes()函数

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 找出DataFrame中指定数据类型的列 select_dtypes()函数 选择题 下列说法错误的是? import pandas as pd myDF pd.DataFrame({A:[1,2],B:[1.0,2.0],C:[a,b]}) print("【显示】myDF&qu…

leecode#同构字符串#反转链表

题目描述&#xff1a; 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同字符不能映射到同一…

ReentrantLock详解

JUC中的锁API 在juc中有一个Lock接口他的作用和synchronized相似都是为了保证线程安全性提供的解决方案 Lock中定义了一系列释放锁和抢占锁相关的API lock() 抢占锁资源 如果当前线程没有抢占到锁 则阻塞 tryLock() 尝试抢占锁资源 如果抢占成功则返回true 否则返回false unlo…

简单的深度活体智能记忆模型

🍿*★,*:.☆欢迎您/$:*.★* 🍿 正文

基于Antd Input组件自定义Input的成功状态

前言 Ant Design的Input组件的有一个状态的Api 需求 公司自研UI组件&#xff0c;在Antd的基础上进行开发。其中Input组件除了警告与错误状态外&#xff0c;还增加了成功的状态。如下图⬇️ 开发实现 方案一&#xff1a;覆盖CSS样式 一开始准备通过判断状态来增加类名&am…

软件工程习题

软件工程第一章 软件与软件工程作业第二章 可行性研究作业第三章 需求分析作业第四章 总体设计作业第五章 详细设计作业第六章 软件编码测验第七章 软件测试作业选择判断简答题第一章 软件与软件工程作业 一、单选题&#xff08;共7题&#xff0c;58.1分&#xff09; 1、软件是…

刷题13-左右两边子数组的和相等

题目012-左右两边子数组的和相等 思路&#xff1a;用到了三个循环&#xff0c;从头到尾遍历数组&#xff0c;比较左右两边数组的和是否相等&#xff0c;当然这种思路时间复杂度也比较高 核心代码&#xff1a; class Solution {public int pivotIndex(int[] nums) {int sum1,…

6.2 、MyBatis 高级映射(resultMap 标签多表联查 , 一对多,多对一关系)

文章目录一、实现多表联查&#xff08;association 标签&#xff09;1、实现多对一关系结果集映射二、实现多表联查&#xff08;collection 标签&#xff09;一、实现多表联查&#xff08;association 标签&#xff09; association 标签&#xff1a; 实现一对一&#xff0c;多…

因果推断1--基本方法介绍(个人笔记)

目录 一、因果推断介绍 1.1 什么是因果推断 1.2为什么研究因果推断 1.3因果推断阶梯 1.4因果推断问题分类 二、因果推断理论框架 2.1 定义&#xff08;这些定义后面会经常用到&#xff09; 2.2 Assumptions&#xff08;三大基本假设&#xff09; 三、因果效应估计 3.1 因果效应…

JavaEE【Spring】:SpringBoot 配置文件

文章目录一、配置文件的作用二、配置文件的格式1、注意2、说明三、properties 配置文件说明1、基本语法2、读取配置文件① 注意3、优缺点四、yml 配置文件说明1、基本语法2、yml 使用进阶① yml 配置不同数据类型及 nullⅠ. yml 配置读取Ⅱ. 练习a. 值为 null 的配置b. 根本不存…

利用云服务器发布项目

前言 平时开发我会写一些小demo&#xff0c;我自己觉得有用的会集中起来形成一个项目&#xff0c;本来想利用gitee的gitee page直接部署出来&#xff0c;但后面了解了下&#xff0c;它只支持官网之类的静态页面&#xff0c;无法与后台数据交互&#xff0c;想要完整的服务还是得…

数据分析业务场景 | 用户画像

一.概况 定义 是根据用户的一系列行为和意识过程建立起来的多维度标签&#xff1b;是根据用户人口学特征&#xff0c;网络浏览内容&#xff0c;网络社交活动和消费行为等信息而抽象出的一个标签化的用户模型&#xff1b;首要任务&#xff1a;根据业务需求整理和数据情况分析建…

Springboot redirect重定向使用RedirecAtrributes传递数据

参考资料 【转载】关于重定向RedirectAttributes的用法RedirectAttributes 的使用 目录前期准备一. RedirecAtrributes重定向传参三. 重定向目标页面接收参数前期准备 ⏹配置文件 server:servlet:context-path: /jmw⏹访问url http://localhost:8080/jmw/test16/init?name…

NX二次开发(C#)-UI Styler-选择对象TaggedObject转换为Body、Face等对象

文章目录 1、前言2、选择对象的过滤器2、选择对象类型为TaggedObject3、TaggedObject转换为Face类型1、前言 前面的博客中已经写过了UI Styler中选择对象(selection)的一些内容,但是依然有读者不知道运用,本文将在前文的基础上更加深入的介绍选择对象的应用(建议与https:/…

DevExpress Universal添加对.NET 7的支持

DevExpress Universal添加对.NET 7的支持 DevExpress已经发布了整个产品系列的主要更新。 CodeRush Ultimate 22.2-为许多重构添加了核心性能优化和增强。 DevExpress.NET MAUI 22.2-添加了对Material Design 3指南的支持&#xff0c;以及对数据网格的自定义过滤、排序和分组。…

PCB封装

目录 1.PCB元器件库分类及命名 1.2PCB封装图形要求 2.封装制作 手工制作封装的操作步骤 1.PCB元器件库分类及命名 元器件采用大写字母表示&#xff0c;PCB元器件库分类及命名如表。 2.PCB封装图形要求 &#xff08;1&#xff09;外形&#xff1a;指元器件的最大外形尺寸。封…

【微电网优化】基于粒子群实现微网经济调度,环境友好调度附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

Java 中的不同参数类型

是不是还傻傻分不清参数配置到底怎么写&#xff0c;写在哪个位置&#xff0c;那么这篇文章就让你学会。 目录 1、Program arguments 2、VM options 3、Environment variables 最佳实践 打开 IDEA 的 Run Configuration&#xff0c;可以看到以下参数配置 VM optionsProgram…