SpringSecurity 认证实现
创始人
2024-04-21 14:46:17
0

在之前一篇 博客 已经说明了 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 {// 拿到认证管理器@ResourceAuthenticationManager authenticationManager;// dto 是用户名和密码 @Overridepublic 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 {@Resourceprivate SysUserMapper sysUserMapper;// 由于返回的是一个 UserDetails 对象,因此还需新建一个继承自 UserDetails 的类@Overridepublic 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 接口,必须实现下面这些方法。@Overridepublic Collection getAuthorities() {return null;}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return sysUser.getUserName();}// 这些方法是用来检查查询到的 User 对象的,都设为 true。@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}// 自定义一个从上下文中获取当前用户的方法public static SysUser getCurrentSystemUser() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication.getPrincipal().equals("anonymous")) {throw new BadCredentialsException("当前为匿名登陆,无法访问");}// 在源码中分析到,最后返回的Authentication 其 principal 属性返回的是一个 UserDetails对象,我们直接可返回 SystemUserDetailSystemUserDetail 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,存入redisString uuid = IdGeneratorUtil.simpleUUID();RedisUtil.getRedisService().set(formatTempAuthCode(uuid), currentSystemUser.getUserName(), JwtTokenUtils.EXPIRE_TIME);// 现在是根据User对象的用户名、缓存中的key,签发tokenreturn 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;// 下面这两个方法用于生成tokenpublic static String generateToken(Map 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 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) {// 从请求头中拿取tokenString 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;@PostConstructvoid init() {snowflake = IdUtil.createSnowflake(workerId, 1);}/*** 获取一个批次号,形如 2019071015301361000101237* 

* 数据库使用 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 依赖


org.springframework.bootspring-boot-starter-data-redis

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

spring:redis:port: 6379database: 0 # 指定数据库编号 host: mastertimeout: 10000lettuce: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 redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate 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 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 hGetAll(String key);/*** 直接设置整个Hash结构*/Boolean hSetAll(String key, Map map, long time);/*** 直接设置整个Hash结构*/void hSetAll(String key, Map 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 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 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 {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic void set(String key, Object value, long time) {set(key, value, time, TimeUnit.SECONDS);}@Overridepublic void set(String key, Object value, long time, TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, time, timeUnit);}@Overridepublic long increment(String key, long time, TimeUnit timeUnit) {RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());redisAtomicLong.expire(time, timeUnit);return redisAtomicLong.incrementAndGet();}@Overridepublic void set(String key, Object value) {redisTemplate.opsForValue().set(key, value);}@Overridepublic Object get(String key) {return redisTemplate.opsForValue().get(key);}@Overridepublic Boolean del(String key) {return redisTemplate.delete(key);}@Overridepublic Long del(List keys) {return redisTemplate.delete(keys);}@Overridepublic Boolean expire(String key, long time) {return redisTemplate.expire(key, time, TimeUnit.SECONDS);}@Overridepublic Boolean expire(String key, long time, TimeUnit timeUnit) {return redisTemplate.expire(key, time, timeUnit);}@Overridepublic Long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}@Overridepublic Boolean hasKey(String key) {return redisTemplate.hasKey(key);}@Overridepublic Long incr(String key, long delta) {return redisTemplate.opsForValue().increment(key, delta);}@Overridepublic Long decr(String key, long delta) {return redisTemplate.opsForValue().increment(key, -delta);}@Overridepublic Object hGet(String key, String hashKey) {return redisTemplate.opsForHash().get(key, hashKey);}@Overridepublic Boolean hSet(String key, String hashKey, Object value, long time) {redisTemplate.opsForHash().put(key, hashKey, value);return expire(key, time);}@Overridepublic void hSet(String key, String hashKey, Object value) {redisTemplate.opsForHash().put(key, hashKey, value);}@Overridepublic Map hGetAll(String key) {return redisTemplate.opsForHash().entries(key);}@Overridepublic Boolean hSetAll(String key, Map map, long time) {redisTemplate.opsForHash().putAll(key, map);return expire(key, time);}@Overridepublic void hSetAll(String key, Map map) {redisTemplate.opsForHash().putAll(key, map);}@Overridepublic void hDel(String key, Object... hashKey) {redisTemplate.opsForHash().delete(key, hashKey);}@Overridepublic Boolean hHasKey(String key, String hashKey) {return redisTemplate.opsForHash().hasKey(key, hashKey);}@Overridepublic Long hIncr(String key, String hashKey, Long delta) {return redisTemplate.opsForHash().increment(key, hashKey, delta);}@Overridepublic Long hDecr(String key, String hashKey, Long delta) {return redisTemplate.opsForHash().increment(key, hashKey, -delta);}@Overridepublic Set sMembers(String key) {return redisTemplate.opsForSet().members(key);}@Overridepublic Long sAdd(String key, Object... values) {return redisTemplate.opsForSet().add(key, values);}@Overridepublic Long sAdd(String key, long time, Object... values) {Long count = redisTemplate.opsForSet().add(key, values);expire(key, time);return count;}@Overridepublic Boolean sIsMember(String key, Object value) {return redisTemplate.opsForSet().isMember(key, value);}@Overridepublic Long sSize(String key) {return redisTemplate.opsForSet().size(key);}@Overridepublic Long sRemove(String key, Object... values) {return redisTemplate.opsForSet().remove(key, values);}@Overridepublic List lRange(String key, long start, long end) {return redisTemplate.opsForList().range(key, start, end);}@Overridepublic Long lSize(String key) {return redisTemplate.opsForList().size(key);}@Overridepublic Object lIndex(String key, long index) {return redisTemplate.opsForList().index(key, index);}@Overridepublic Long lPush(String key, Object value) {return redisTemplate.opsForList().rightPush(key, value);}@Overridepublic Long lPush(String key, Object value, long time) {Long index = redisTemplate.opsForList().rightPush(key, value);expire(key, time);return index;}@Overridepublic Long lPushAll(String key, Object... values) {return redisTemplate.opsForList().rightPushAll(key, values);}@Overridepublic Long lPushAll(String key, Long time, Object... values) {Long count = redisTemplate.opsForList().rightPushAll(key, values);expire(key, time);return count;}@Overridepublic 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.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 ignoreUrlList;}

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

@Resource
private IgnoreUrlConfig ignoreUrlConfig;ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
// 放行的路径
for (String url : ignoreUrlConfig.getIgnoreUrlList()) {registry.antMatchers(url).permitAll();
}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...