SpringSecurity Demo实操
Builder链式添加测试数据
如果只是想测试某一项技术,但是又不想通过数据库去写查询接口来操作,就可以在项目启动之初初始化一个Map或ArrayList来模拟数据库,下面具体操作代码:
/**
* 存放默认用户信息
*/
private List<AdminUserDetails> adminUserDetailsList = new ArrayList<>();
/**
* 存放默认资源信息
*/
private List<UmsResource> resourceList = new ArrayList<>();
@Autowired
private PasswordEncoder passwordEncoder;
// todo 后续改为用数据库查数据
@PostConstruct
private void init(){
adminUserDetailsList.add(AdminUserDetails.builder()
.username("admin")
.password(passwordEncoder.encode("123456"))
.authorityList(CollUtil.toList("1:brand:create","2:brand:update","3:brand:delete","4:brand:list","5:brand:listAll"))
.build());
adminUserDetailsList.add(AdminUserDetails.builder()
.username("macro")
.password(passwordEncoder.encode("123456"))
.authorityList(CollUtil.toList("5:brand:listAll"))
.build());
resourceList.add(UmsResource.builder()
.id(1L)
.name("brand:create")
.url("/brand/create")
.build());
resourceList.add(UmsResource.builder()
.id(2L)
.name("brand:update")
.url("/brand/update/**")
.build());
resourceList.add(UmsResource.builder()
.id(3L)
.name("brand:delete")
.url("/brand/delete/**")
.build());
resourceList.add(UmsResource.builder()
.id(4L)
.name("brand:list")
.url("/brand/list")
.build());
resourceList.add(UmsResource.builder()
.id(5L)
.name("brand:listAll")
.url("/brand/listAll")
.build());
}
比较有意思的是这里用到了builder链式填充的形式,可以看定义类的写法:
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class AdminUserDetails implements UserDetails {
private String username;
private String password;
private List<String> authorityList;
}
这里需要先加上注解@Builder才能支持链式调用。
言归正传,回到SpringSecurity,这里介绍下这里的业务场景:模拟一个登录请求,其中authorityList是权限列表,存储当前用户具备的一些权限(注意这里的写法同SpringSecurity这个方法的参数保持一致)
然后这个类它是继承UserDetails,这个是属于SpringSecurity其下的,它包含一些对账号密码的常用API方法,更为安全吧,直接重写即可:
SpringSecurity最核心的就是要补充完善这样一个配置类,下面是一个案例:
@Configuration
public class SecurityConfig {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
// 处理器,拒绝策略
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private DynamicSecurityService dynamicSecurityService;
@Autowired
private DynamicSecurityFilter dynamicSecurityFilter;
// 配置过滤器等其他配置
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
//不需要保护的资源路径允许访问
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS)
.permitAll();
httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
.disable()
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()// 除上面外的所有请求全部需要鉴权认证
.authenticated();
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter(确定过滤器的顺序)
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登录结果返回
httpSecurity.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);
//有动态权限配置时添加动态权限校验过滤器
if(dynamicSecurityService!=null){
registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
}
return httpSecurity.build();
}
}
在配置类中,可以发现它是按照顺序依次进行处理的,就很像日常写的业务逻辑处理,然后其中需要用到什么组件就去新增即可,比较熟悉的就是添加过滤器,这里是两个:JWT校验和权限校验
登录流程:
用户成功登录,后台依靠jwt为用户生成一个密钥,之后携带在每次请求头中。我们知道jwt是无状态的,首先肯定是有个过滤器,对所有请求进行校验(当然也需要为登录注册接口设置白名单),之后的所有请求都会通过过滤器进行处理,过滤器核心逻辑就是校验jwt是否有效。
在选择继承的过滤器,建议选用 OncePerRequestFilter , 确保 在每次请求中只被执行 一次( 有时在复杂的请求链中,过滤器可能会因为转发或重定向等操作被重复调用)
public class MyCustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 执行过滤逻辑,比如检查请求头、设置属性、日志记录等
System.out.println("Executing custom filter logic");
// 调用过滤器链中的下一个过滤器或最终的资源处理器
filterChain.doFilter(request, response);
}
}
上面是使用jwt来保证了用户会话的持久性,但不同角色的权限控制怎么实现呢?下面就该SpringSecurity上场了。
------未完待续
SpringSecurity权限控制