当前位置: 首页 > news >正文

深圳工信部网站备案信息查询长宁区网站制作设计

深圳工信部网站备案信息查询,长宁区网站制作设计,网站建设对企业带来什么作用,鞋帽网站欣赏Spring Security 在最近几个版本中配置的写法都有一些变化#xff0c;很多常见的方法都废弃了#xff0c;并且将在未来的 Spring Security7 中移除#xff0c;因此又补充了一些新的内容#xff0c;重新发一下#xff0c;供各位使用 Spring Security 的小伙伴们参考。 接下…Spring Security 在最近几个版本中配置的写法都有一些变化很多常见的方法都废弃了并且将在未来的 Spring Security7 中移除因此又补充了一些新的内容重新发一下供各位使用 Spring Security 的小伙伴们参考。 接下来我把从 Spring Security5.7 开始对应 Spring Boot2.7 开始各种已知的变化都来和小伙伴们梳理一下。 1. WebSecurityConfigurerAdapter 首先第一点就是各位小伙伴最容易发现的 WebSecurityConfigurerAdapter 过期了在目前最新的 Spring Security6.1 中这个类已经完全被移除了想凑合着用都不行了。 准确来说Spring Security 是在 5.7.0-M2 这个版本中将 WebSecurityConfigurerAdapter 过期的过期的原因是因为官方想要鼓励各位开发者使用基于组件的安全配置。 那么什么是基于组件的安全配置呢我们来举几个例子 以前我们配置 SecurityFilterChain 的方式是下面这样 Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter {Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authz) - authz.anyRequest().authenticated()).httpBasic(withDefaults());}}那么以后就要改为下面这样了 Configuration public class SecurityConfiguration {Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authz) - authz.anyRequest().authenticated()).httpBasic(withDefaults());return http.build();}}如果懂之前的写法的话下面这个代码其实是很好理解的我就不做过多解释了不过还不懂 Spring Security 基本用法的小伙伴 以前我们配置 WebSecurity 是这样 Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter {Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers(/ignore1, /ignore2);}}以后就得改成下面这样了 Configuration public class SecurityConfiguration {Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) - web.ignoring().antMatchers(/ignore1, /ignore2);}}另外还有一个就是关于 AuthenticationManager 的获取以前可以通过重写父类的方法来获取这个 Bean类似下面这样 Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {OverrideBeanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();} }以后就只能自己创建这个 Bean 了类似下面这样 Configuration public class SecurityConfig {AutowiredUserService userService;BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(userService);ProviderManager pm new ProviderManager(daoAuthenticationProvider);return pm;} }当然也可以从 HttpSecurity 中提取出来 AuthenticationManager如下 Configuration public class SpringSecurityConfiguration {AuthenticationManager authenticationManager;AutowiredUserDetailsService userDetailsService;Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {AuthenticationManagerBuilder authenticationManagerBuilder http.getSharedObject(AuthenticationManagerBuilder.class);authenticationManagerBuilder.userDetailsService(userDetailsService);authenticationManager authenticationManagerBuilder.build();http.csrf().disable().cors().disable().authorizeHttpRequests().antMatchers(/api/v1/account/register, /api/v1/account/auth).permitAll().anyRequest().authenticated().and().authenticationManager(authenticationManager).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);return http.build();}}这也是一种办法。 我们来看一个具体的例子。 首先我们新建一个 Spring Boot 工程引入 Web 和 Spring Security 依赖注意 Spring Boot 选择最新版。 接下来我们提供一个简单的测试接口如下 RestController public class HelloController {GetMapping(/hello)public String hello() {return hello 江南一点雨!;} }小伙伴们知道在 Spring Security 中默认情况下只要添加了依赖我们项目的所有接口就已经被统统保护起来了现在启动项目访问 /hello 接口就需要登录之后才可以访问登录的用户名是 user密码则是随机生成的在项目的启动日志中。 现在我们的第一个需求是使用自定义的用户而不是系统默认提供的这个简单我们只需要向 Spring 容器中注册一个 UserDetailsService 的实例即可像下面这样 Configuration public class SecurityConfig {BeanUserDetailsService userDetailsService() {InMemoryUserDetailsManager users new InMemoryUserDetailsManager();users.createUser(User.withUsername(javaboy).password({noop}123).roles(admin).build());users.createUser(User.withUsername(江南一点雨).password({noop}123).roles(admin).build());return users;}}这就可以了。 当然我现在的用户是存在内存中的如果你的用户是存在数据库中那么只需要提供 UserDetailsService 接口的实现类并注入 Spring 容器即可这个之前在 vhr 视频中讲过多次了公号后台回复 666 有视频介绍这里就不再赘述了。 但是假如说我希望 /hello 这个接口能够匿名访问并且我希望这个匿名访问还不经过 Spring Security 过滤器链要是在以前我们可以重写 configure(WebSecurity) 方法进行配置但是现在得换一种玩法 Configuration public class SecurityConfig {BeanUserDetailsService userDetailsService() {InMemoryUserDetailsManager users new InMemoryUserDetailsManager();users.createUser(User.withUsername(javaboy).password({noop}123).roles(admin).build());users.createUser(User.withUsername(江南一点雨).password({noop}123).roles(admin).build());return users;}BeanWebSecurityCustomizer webSecurityCustomizer() {return new WebSecurityCustomizer() {Overridepublic void customize(WebSecurity web) {web.ignoring().antMatchers(/hello);}};}}以前位于 configure(WebSecurity) 方法中的内容现在位于 WebSecurityCustomizer Bean 中该配置的东西写在这里就可以了。 那如果我还希望对登录页面参数等进行定制呢继续往下看 Configuration public class SecurityConfig {BeanUserDetailsService userDetailsService() {InMemoryUserDetailsManager users new InMemoryUserDetailsManager();users.createUser(User.withUsername(javaboy).password({noop}123).roles(admin).build());users.createUser(User.withUsername(江南一点雨).password({noop}123).roles(admin).build());return users;}BeanSecurityFilterChain securityFilterChain() {ListFilter filters new ArrayList();return new DefaultSecurityFilterChain(new AntPathRequestMatcher(/**), filters);}}Spring Security 的底层实际上就是一堆过滤器所以我们之前在 configure(HttpSecurity) 方法中的配置实际上就是配置过滤器链。现在过滤器链的配置我们通过提供一个 SecurityFilterChain Bean 来配置过滤器链SecurityFilterChain 是一个接口这个接口只有一个实现类 DefaultSecurityFilterChain构建 DefaultSecurityFilterChain 的第一个参数是拦截规则也就是哪些路径需要拦截第二个参数则是过滤器链这里我给了一个空集合也就是我们的 Spring Security 会拦截下所有的请求然后在一个空集合中走一圈就结束了相当于不拦截任何请求。 此时重启项目你会发现 /hello 也是可以直接访问的就是因为这个路径不经过任何过滤器。 其实我觉得目前这中新写法比以前老的写法更直观更容易让大家理解到 Spring Security 底层的过滤器链工作机制。 有小伙伴会说这写法跟我以前写的也不一样呀这么配置我也不知道 Spring Security 中有哪些过滤器其实换一个写法我们就可以将这个配置成以前那种样子 Configuration public class SecurityConfig {BeanUserDetailsService userDetailsService() {InMemoryUserDetailsManager users new InMemoryUserDetailsManager();users.createUser(User.withUsername(javaboy).password({noop}123).roles(admin).build());users.createUser(User.withUsername(江南一点雨).password({noop}123).roles(admin).build());return users;}BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().permitAll().and().csrf().disable();return http.build();}}这么写就跟以前的写法其实没啥大的差别了。 2. 使用 Lambda 在最新版中小伙伴们发现很多常见的方法废弃了如下图 包括大家熟悉的用来连接各个配置项的 and() 方法现在也废弃了并且按照官方的说法将在 Spring Security7 中彻底移除该方法。 也就是说你以后见不到类似下面这样的配置了 Override protected void configure(HttpSecurity http) throws Exception {InMemoryUserDetailsManager users new InMemoryUserDetailsManager();users.createUser(User.withUsername(javagirl).password({noop}123).roles(admin).build());http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().csrf().disable().userDetailsService(users);http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class); }and() 方法将被移除 从上面 and 方法的注释中小伙伴们可以看到官方现在是在推动基于 Lambda 的配置来代替传统的链式配置所以以后我们的写法就得改成下面这样啦 Configuration public class SecurityConfig {BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth - auth.requestMatchers(/hello).hasAuthority(user).anyRequest().authenticated()).formLogin(form - form.loginProcessingUrl(/login).usernameParameter(name).passwordParameter(passwd)).csrf(csrf - csrf.disable()).sessionManagement(session - session.maximumSessions(1).maxSessionsPreventsLogin(true));return http.build();} }其实这里的几个方法倒不是啥新方法只不过有的小伙伴可能之前不太习惯用上面这几个方法进行配置习惯于链式配置。可是往后就得慢慢习惯上面这种按照 Lambda 的方式来配置了配置的内容倒很好理解我觉得没啥好解释的。 3. 自定义 JSON 登录 自定义 JSON 登录也和之前旧版不太一样了。 3.1 自定义 JSON 登录 小伙伴们知道Spring Security 中默认的登录接口数据格式是 key-value 的形式如果我们想使用 JSON 格式来登录那么就必须自定义过滤器或者自定义登录接口下面先来和小伙伴们展示一下这两种不同的登录形式。 3.1.1 自定义登录过滤器 Spring Security 默认处理登录数据的过滤器是 UsernamePasswordAuthenticationFilter在这个过滤器中系统会通过 request.getParameter(this.passwordParameter) 的方式将用户名和密码读取出来很明显这就要求前端传递参数的形式是 key-value。 如果想要使用 JSON 格式的参数登录那么就需要从这个地方做文章了我们自定义的过滤器如下 public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//获取请求头据此判断请求参数类型String contentType request.getContentType();if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(contentType)) {//说明请求参数是 JSONif (!request.getMethod().equals(POST)) {throw new AuthenticationServiceException(Authentication method not supported: request.getMethod());}String username null;String password null;try {//解析请求体中的 JSON 参数User user new ObjectMapper().readValue(request.getInputStream(), User.class);username user.getUsername();username (username ! null) ? username.trim() : ;password user.getPassword();password (password ! null) ? password : ;} catch (IOException e) {throw new RuntimeException(e);}//构建登录令牌UsernamePasswordAuthenticationToken authRequest UsernamePasswordAuthenticationToken.unauthenticated(username,password);// Allow subclasses to set the details propertysetDetails(request, authRequest);//执行真正的登录操作Authentication auth this.getAuthenticationManager().authenticate(authRequest);return auth;} else {return super.attemptAuthentication(request, response);}} }看过之前的 Spring Security 系列文章的小伙伴这段代码应该都是非常熟悉了。 首先我们获取请求头根据请求头的类型来判断请求参数的格式。如果是 JSON 格式的参数就在 if 中进行处理否则说明是 key-value 形式的参数那么我们就调用父类的方法进行处理即可。JSON 格式的参数的处理逻辑和 key-value 的处理逻辑是一致的唯一不同的是参数的提取方式不同而已。 最后我们还需要对这个过滤器进行配置 Configuration public class SecurityConfig {AutowiredUserService userService;BeanJsonLoginFilter jsonLoginFilter() {JsonLoginFilter filter new JsonLoginFilter();filter.setAuthenticationSuccessHandler((req,resp,auth)-{resp.setContentType(application/json;charsetutf-8);PrintWriter out resp.getWriter();//获取当前登录成功的用户对象User user (User) auth.getPrincipal();user.setPassword(null);RespBean respBean RespBean.ok(登录成功, user);out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationFailureHandler((req,resp,e)-{resp.setContentType(application/json;charsetutf-8);PrintWriter out resp.getWriter();RespBean respBean RespBean.error(登录失败);if (e instanceof BadCredentialsException) {respBean.setMessage(用户名或者密码输入错误登录失败);} else if (e instanceof DisabledException) {respBean.setMessage(账户被禁用登录失败);} else if (e instanceof CredentialsExpiredException) {respBean.setMessage(密码过期登录失败);} else if (e instanceof AccountExpiredException) {respBean.setMessage(账户过期登录失败);} else if (e instanceof LockedException) {respBean.setMessage(账户被锁定登录失败);}out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationManager(authenticationManager());filter.setFilterProcessesUrl(/login);return filter;}BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(userService);ProviderManager pm new ProviderManager(daoAuthenticationProvider);return pm;}BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {//开启过滤器的配置http.authorizeHttpRequests()//任意请求都要认证之后才能访问.anyRequest().authenticated().and()//开启表单登录开启之后就会自动配置登录页面、登录接口等信息.formLogin()//和登录相关的 URL 地址都放行.permitAll().and()//关闭 csrf 保护机制本质上就是从 Spring Security 过滤器链中移除了 CsrfFilter.csrf().disable();http.addFilterBefore(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class);return http.build();}}这里就是配置一个 JsonLoginFilter 的 Bean并将之添加到 Spring Security 过滤器链中即可。 在 Spring Boot3 之前Spring Security6 之前上面这段代码就可以实现 JSON 登录了。 但是从 Spring Boot3 开始这段代码有点瑕疵了直接用已经无法实现 JSON 登录了具体原因下文分析。 3.1.2 自定义登录接口 另外一种自定义 JSON 登录的方式是直接自定义登录接口如下 RestController public class LoginController {AutowiredAuthenticationManager authenticationManager;PostMapping(/doLogin)public String doLogin(RequestBody User user) {UsernamePasswordAuthenticationToken unauthenticated UsernamePasswordAuthenticationToken.unauthenticated(user.getUsername(), user.getPassword());try {Authentication authenticate authenticationManager.authenticate(unauthenticated);SecurityContextHolder.getContext().setAuthentication(authenticate);return success;} catch (AuthenticationException e) {return error: e.getMessage();}} }这里直接自定义登录接口请求参数通过 JSON 的形式来传递。拿到用户名密码之后调用 AuthenticationManager#authenticate 方法进行认证即可。认证成功之后将认证后的用户信息存入到 SecurityContextHolder 中。 最后再配一下登录接口就行了 Configuration public class SecurityConfig {AutowiredUserService userService;BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider provider new DaoAuthenticationProvider();provider.setUserDetailsService(userService);ProviderManager pm new ProviderManager(provider);return pm;}BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests()//表示 /doLogin 这个地址可以不用登录直接访问.requestMatchers(/doLogin).permitAll().anyRequest().authenticated().and().formLogin().permitAll().and().csrf().disable();return http.build();} }这也算是一种使用 JSON 格式参数的方案。在 Spring Boot3 之前Spring Security6 之前上面这个方案也是没有任何问题的。 从 Spring Boot3Spring Security6 开始上面这两种方案都出现了一些瑕疵。 具体表现就是当你调用登录接口登录成功之后再去访问系统中的其他页面又会跳转回登录页面说明访问登录之外的其他接口时系统不知道你已经登录过了。 3.2 原因分析 产生上面问题的原因主要在于 Spring Security 过滤器链中有一个过滤器发生变化了 在 Spring Boot3 之前Spring Security 过滤器链中有一个名为 SecurityContextPersistenceFilter 的过滤器这个过滤器在 Spring Boot2.7.x 中废弃了但是还在使用在 Spring Boot3 中则被从 Spring Security 过滤器链中移除了取而代之的是一个名为 SecurityContextHolderFilter 的过滤器。 在第一小节和小伙伴们介绍的两种 JSON 登录方案在 Spring Boot2.x 中可以运行在 Spring Boot3.x 中无法运行就是因为这个过滤器的变化导致的。 所以接下来我们就来分析一下这两个过滤器到底有哪些区别。 先来看 SecurityContextPersistenceFilter 的核心逻辑 private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {HttpRequestResponseHolder holder new HttpRequestResponseHolder(request, response);SecurityContext contextBeforeChainExecution this.repo.loadContext(holder);try {SecurityContextHolder.setContext(contextBeforeChainExecution);chain.doFilter(holder.getRequest(), holder.getResponse());}finally {SecurityContext contextAfterChainExecution SecurityContextHolder.getContext();SecurityContextHolder.clearContext();this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());} }我这里只贴出来了一些关键的核心代码 首先这个过滤器位于整个 Spring Security 过滤器链的第三个是非常靠前的。当登录请求经过这个过滤器的时候首先会尝试从 SecurityContextRepository上文中的 this.repo中读取到 SecurityContext 对象这个对象中保存了当前用户的信息第一次登录的时候这里实际上读取不到任何用户信息。将读取到的 SecurityContext 存入到 SecurityContextHolder 中默认情况下SecurityContextHolder 中通过 ThreadLocal 来保存 SecurityContext 对象也就是当前请求在后续的处理流程中只要在同一个线程里都可以直接从 SecurityContextHolder 中提取到当前登录用户信息。请求继续向后执行。在 finally 代码块中当前请求已经结束了此时再次获取到 SecurityContext并清空 SecurityContextHolder 防止内存泄漏然后调用 this.repo.saveContext 方法保存当前登录用户对象实际上是保存到 HttpSession 中。以后其他请求到达的时候执行前面第 2 步的时候就读取到当前用户的信息了在请求后续的处理过程中Spring Security 需要知道当前用户的时候会自动去 SecurityContextHolder 中读取当前用户信息。 这就是 Spring Security 认证的一个大致流程。 然而到了 Spring Boot3 之后这个过滤器被 SecurityContextHolderFilter 取代了我们来看下 SecurityContextHolderFilter 过滤器的一个关键逻辑 private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {SupplierSecurityContext deferredContext this.securityContextRepository.loadDeferredContext(request);try {this.securityContextHolderStrategy.setDeferredContext(deferredContext);chain.doFilter(request, response);}finally {this.securityContextHolderStrategy.clearContext();request.removeAttribute(FILTER_APPLIED);} }小伙伴们看到前面的逻辑基本上还是一样的不一样的是 finally 中的代码finally 中少了一步向 HttpSession 保存 SecurityContext 的操作。 这下就明白了用户登录成功之后用户信息没有保存到 HttpSession导致下一次请求到达的时候无法从 HttpSession 中读取到 SecurityContext 存到 SecurityContextHolder 中在后续的执行过程中Spring Security 就会认为当前用户没有登录。 这就是问题的原因 找到原因那么问题就好解决了。 3.3 问题解决 首先问题出在了过滤器上直接改过滤器倒也不是不可以但是既然 Spring Security 在升级的过程中抛弃了之前旧的方案我们又费劲的把之前旧的方案写回来好像也不合理。 其实Spring Security 提供了另外一个修改的入口在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication 方法中源码如下 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication authResult) throws IOException, ServletException {SecurityContext context this.securityContextHolderStrategy.createEmptyContext();context.setAuthentication(authResult);this.securityContextHolderStrategy.setContext(context);this.securityContextRepository.saveContext(context, request, response);this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher ! null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult); }这个方法是当前用户登录成功之后的回调方法小伙伴们看到在这个回调方法中有一句 this.securityContextRepository.saveContext(context, request, response);这就表示将当前登录成功的用户信息存入到 HttpSession 中。 在当前过滤器中securityContextRepository 的类型是 RequestAttributeSecurityContextRepository这个表示将 SecurityContext 存入到当前请求的属性中那很明显在当前请求结束之后这个数据就没了。在 Spring Security 的自动化配置类中将 securityContextRepository 属性指向了 DelegatingSecurityContextRepository这是一个代理的存储器代理的对象是 RequestAttributeSecurityContextRepository 和 HttpSessionSecurityContextRepository所以在默认的情况下用户登录成功之后在这里就把登录用户数据存入到 HttpSessionSecurityContextRepository 中了。 当我们自定义了登录过滤器之后就破坏了自动化配置里的方案了这里使用的 securityContextRepository 对象就真的是 RequestAttributeSecurityContextRepository 了所以就导致用户后续访问时系统以为用户未登录。 那么解决方案很简单我们只需要为自定义的过滤器指定 securityContextRepository 属性的值就可以了如下 Bean JsonLoginFilter jsonLoginFilter() {JsonLoginFilter filter new JsonLoginFilter();filter.setAuthenticationSuccessHandler((req,resp,auth)-{resp.setContentType(application/json;charsetutf-8);PrintWriter out resp.getWriter();//获取当前登录成功的用户对象User user (User) auth.getPrincipal();user.setPassword(null);RespBean respBean RespBean.ok(登录成功, user);out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationFailureHandler((req,resp,e)-{resp.setContentType(application/json;charsetutf-8);PrintWriter out resp.getWriter();RespBean respBean RespBean.error(登录失败);if (e instanceof BadCredentialsException) {respBean.setMessage(用户名或者密码输入错误登录失败);} else if (e instanceof DisabledException) {respBean.setMessage(账户被禁用登录失败);} else if (e instanceof CredentialsExpiredException) {respBean.setMessage(密码过期登录失败);} else if (e instanceof AccountExpiredException) {respBean.setMessage(账户过期登录失败);} else if (e instanceof LockedException) {respBean.setMessage(账户被锁定登录失败);}out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationManager(authenticationManager());filter.setFilterProcessesUrl(/login);filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());return filter; }小伙伴们看到最后调用 setSecurityContextRepository 方法设置一下就行。 Spring Boot3.x 之前之所以不用设置这个属性是因为这里虽然没保存最后还是在 SecurityContextPersistenceFilter 过滤器中保存了。 那么对于自定义登录接口的问题解决思路也是类似的 RestController public class LoginController {AutowiredAuthenticationManager authenticationManager;PostMapping(/doLogin)public String doLogin(RequestBody User user, HttpSession session) {UsernamePasswordAuthenticationToken unauthenticated UsernamePasswordAuthenticationToken.unauthenticated(user.getUsername(), user.getPassword());try {Authentication authenticate authenticationManager.authenticate(unauthenticated);SecurityContextHolder.getContext().setAuthentication(authenticate);session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());return success;} catch (AuthenticationException e) {return error: e.getMessage();}} }小伙伴们看到在登录成功之后开发者自己手动将数据存入到 HttpSession 中这样就能确保下个请求到达的时候能够从 HttpSession 中读取到有效的数据存入到 SecurityContextHolder 中了。 4. 该如何实现动态权限管理 4.1. 权限开发思路 先来说权限开发的思路当我们设计好 RBAC 权限之后具体到代码层面我们有两种实现思路 直接在接口/Service 层方法上添加权限注解这样做的好处是实现简单但是有一个问题就是权限硬编码每一个方法需要什么权限都是代码中配置好的后期如果想通过管理页面修改是不可能的要修改某一个方法所需要的权限只能改代码。将请求和权限的关系通过数据库来描述每一个请求需要什么权限都在数据库中配置好当请求到达的时候动态查询然后判断权限是否满足这样做的好处是比较灵活将来需要修改接口和权限之间的关系时可以通过管理页面点击几下问题就解决了不用修改代码松哥之前的 vhr 中就是这样做的。 4.2. 具体实践 4.2.1 旧方案回顾 第一个类是收集权限元数据的类 Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {Overridepublic CollectionConfigAttribute getAttributes(Object object) throws IllegalArgumentException {//...}Overridepublic CollectionConfigAttribute getAllConfigAttributes() {return null;}Overridepublic boolean supports(Class? clazz) {return true;} }在 getAttributes 方法中根据当前请求的 URL 地址从参数 Object 中可提取出来然后根据权限表中的配置分析出来当前请求需要哪些权限并返回。 另外我还重写了一个决策器其实决策器也可以不重写就看你自己的需求如果 Spring Security 自带的决策器无法满足你的需求那么可以自己写一个决策器 Component public class CustomUrlDecisionManager implements AccessDecisionManager {Overridepublic void decide(Authentication authentication, Object object, CollectionConfigAttribute configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {//...}Overridepublic boolean supports(ConfigAttribute attribute) {return true;}Overridepublic boolean supports(Class? clazz) {return true;} }decide 方法就是做决策的地方第一个参数中可以提取出当前用户具备什么权限第三个参数是当前请求需要什么权限比较一下就行了如果当前用户不具备需要的权限则直接抛出 AccessDeniedException 异常即可。 最后通过 Bean 的后置处理器 BeanPostProcessor将这两个配置类放到 Spring Security 的 FilterSecurityInterceptor 拦截器中 Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessorFilterSecurityInterceptor() {Overridepublic O extends FilterSecurityInterceptor O postProcess(O object) {object.setAccessDecisionManager(customUrlDecisionManager);object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);return object;}}).and()//... }大致上的逻辑就是如此 4.2.2 新方案 不过以上代码在目前最新的 Spring Security6 中用不了了不是因为类过期了而是因为类被移除了哪个类被移除了FilterSecurityInterceptor。 FilterSecurityInterceptor 这个过滤器以前是做权限处理的但是在新版的 Spring Security6 中这个拦截器被 AuthorizationFilter 代替了。 老实说新版的方案其实更合理一些传统的方案感觉带有很多前后端不分的影子现在就往更纯粹的前后端分离奔去。 由于新版中连 FilterSecurityInterceptor 都不用了所以旧版的方案显然行不通了新版的方案实际上更加简单。 虽然新旧写法不同但是核心思路是一模一样。 我们来看下新版的配置 Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(register - register.anyRequest().access((authentication, object) - {//表示请求的 URL 地址和数据库的地址是否匹配上了boolean isMatch false;//获取当前请求的 URL 地址String requestURI object.getRequest().getRequestURI();ListMenuWithRoleVO menuWithRole menuService.getMenuWithRole();for (MenuWithRoleVO m : menuWithRole) {if (antPathMatcher.match(m.getUrl(), requestURI)) {isMatch true;//说明找到了请求的地址了//这就是当前请求需要的角色ListRole roles m.getRoles();//获取当前登录用户的角色Collection? extends GrantedAuthority authorities authentication.get().getAuthorities();for (GrantedAuthority authority : authorities) {for (Role role : roles) {if (authority.getAuthority().equals(role.getName())) {//说明当前登录用户具备当前请求所需要的角色return new AuthorizationDecision(true);}}}}}if (!isMatch) {//说明请求的 URL 地址和数据库的地址没有匹配上对于这种请求统一只要登录就能访问if (authentication.get() instanceof AnonymousAuthenticationToken) {return new AuthorizationDecision(false);} else {//说明用户已经认证了return new AuthorizationDecision(true);}}return new AuthorizationDecision(false);})).formLogin(form - //...).csrf(csrf - //...).exceptionHandling(e - //...).logout(logout -//...);return http.build(); }核心思路还是和之前一样只不过现在的工作都在 access 方法中完成。 access 方法的回调中有两个参数第一个参数是 authentication很明显这就是当前登录成功的用户对象从这里我们就可以提取出来当前用户所具备的权限。 第二个参数 object 实际上是一个 RequestAuthorizationContext从这个里边可以提取出来当前请求对象 HttpServletRequest进而提取出来当前请求的 URL 地址然后依据权限表中的信息判断出当前请求需要什么权限再和 authentication 中提取出来的当前用户所具备的权限进行对比即可。 如果当前登录用户具备请求所需要的权限则返回 new AuthorizationDecision(true);否则返回 new AuthorizationDecision(false); 即可。 其实无论什么框架只要能把其中一个版本掌握个 70%以后无论它怎么升级你都能快速上手
http://www.dnsts.com.cn/news/54624.html

相关文章:

  • 做的网站进不去后台如何创建一个简单的网页
  • 公司网站自己怎么建立如何注册个人网站
  • 网站接任务来做成都网站建站公司
  • 全国十大网站建设公司排名php网站源码安装教程
  • 网站服务器租一个月最近播放中文版在线观看电视剧
  • 龙华观澜网站建设做微信广告网站
  • 有哪些做投行网站绵阳学校网站建设
  • 泸州市网站建设wordpress备份文件
  • html网站开发事例教程新余市建设局网站
  • 深圳商城网站设计公司wordpress文章类插件
  • 乐都网站建设多少钱做项目搭建网站 构建数据库
  • 做网站用矢量图还是位图贵阳公司官方网站建设
  • 网站建设应该应聘什么岗位有个人做网站的
  • 做装修效果图的网站有哪些软件下载怎么做网站布局
  • 建设学校网站完整的网站建设平台协议书
  • 如何建设视频网站北京网站优化价格
  • 鸿运通网站建设怎么样吉林网络公司网站建设
  • 深圳公司网页设计推广报价网站优化建设哈尔滨
  • 建设部网站不支持360网站联盟的收益模式
  • 做pc端软件可以参考那些网站响应式网站是什么
  • 做外链音乐网站大型网站开发周期
  • 网站建设需要几步网站建设怎样中英文
  • 网站流量的转换率一般是多少南沙商城网站建设
  • 网站管理员权限设置权限设置h5免费建站
  • 官方网站 建设情况汇报淘宝客导购网站
  • 音乐网站html模板怎么制作一个网站教程
  • 手机网站策划书wordpress视频无法播放视频播放器
  • 网站建设文档模板如何做一个论坛网站
  • 哪个网站设计好wordpress如何爬虫
  • 做网站包括图片设计吗专业定制网站设计