Spring Security6入门及自定义登录

这篇具有很好参考价值的文章主要介绍了Spring Security6入门及自定义登录。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Spring Security已经更新到了6.x,很多常见的方法都废弃了,并且将在未来的 Spring Security7 中移除,主要总结几点变化。

本文代码环境jdk版本17、boot版本3.1.2、security6.1.2

1、WebSecurityConfigurerAdapter 过期了

准确来说,Spring Security 是在 5.7.0-M2 这个版本中将 WebSecurityConfigurerAdapter 过期的,过期的原因是因为官方想要鼓励各位开发者使用基于组件的安全配置。没有过期的写法如下

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
    }

//配置 WebSecurity 
@Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/ignore1", "/ignore2");
    }

}

现在的写法如下 

/**
 * 5版本只需@Configuration一个注解,不需要@EnableWebSecurity,
 * 6需要同时引入,并且5是需要extends WebSecurityConfigurerAdapter类
 */
@Configuration
@EnableWebSecurity
public class MySecurityConfig {
    /**
     * 5版本是override 方法: configure(HttpSecurity http),6是下面bean
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        //authorizeHttpRequests:针对http请求进行授权认证,定义不需要认证就能的资源路径,如info
        http.authorizeHttpRequests(authorizeHttpRequests->authorizeHttpRequests
                .requestMatchers("/info").permitAll()
                .anyRequest().authenticated());

        /**登录表单配置
         * loginPage:登录页面请求地址
         * loginProcessingUrl:登录接口 过滤器
         * successForwardUrl:登录成功响应地址
         * failureForwardUrl:登录失败响应地址
         */
        http.formLogin(formLogin->formLogin
                .loginPage("/toLoginPage").permitAll()
                .loginProcessingUrl("/login")
                .successForwardUrl("/index")
                .failureForwardUrl("/toLoginPage"));

        //关闭crsf 跨域漏洞防御
        http.csrf(withDefaults());//相当于 http.csrf(Customizer.withDefaults());或者http.csrf(crsf->crsf.disable());

        //退出
        http.logout(logout -> logout.invalidateHttpSession(true));

        return http.build();
    }

//配置 WebSecurity 
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers("/ignore1", "/ignore2");
    }
}

 2、AuthenticationManager的获取

以前可以通过重写父类的方法来获取这个 Bean,类似下面这样

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

6.x中只能自己创建这个 Bean 了

@Configuration
public class SecurityConfig {

    @Autowired
    UserService userService;

    @Bean
    AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userService);
        ProviderManager pm = new ProviderManager(daoAuthenticationProvider);
        return pm;
    }
}

当然,也可以从 HttpSecurity 中提取出来 AuthenticationManager,如下:

@Configuration
@EnableWebSecurity
public class MySecurityConfig {
   
    AuthenticationManager authenticationManager;
    @Resource
    UserDetailsServiceImpl userDetailsService;
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.userDetailsService(userDetailsService);
        authenticationManager = authenticationManagerBuilder.build();
        
        //不需要认证的请求
        return http.authorizeHttpRequests(conf -> conf.requestMatchers("/login", "/info").permitAll().anyRequest().authenticated())
                //通过new JwtFilter()的方式,而不是bean组件注入的方式
                .addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf(withDefaults())
                .cors(withDefaults()).build();
    }

}

3、and去除,使用Lambda

以前使用and类似如下代码

@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);
}

但是6中and() 方法被移除!使用Lambda写法如下

@Configuration
@EnableWebSecurity
public class MySecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //不需要认证的请求
        return http.authorizeHttpRequests(conf -> conf.requestMatchers("/login", "/info").permitAll().anyRequest().authenticated())
                //通过new JwtFilter()的方式,而不是bean组件注入的方式
                .addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf(withDefaults())
                .cors(withDefaults()).build();
    }
  
}

4、自定义json登录

关于使用json登录有两种方式,自定json登录过滤器和自定义json登录接口

自定json登录过滤器如下

public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public 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)) {
            //说明请求参数是 JSON
            if (!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" property
            setDetails(request, authRequest);
            //执行真正的登录操作
            Authentication auth = this.getAuthenticationManager().authenticate(authRequest);
            return auth;
        } else {
            return super.attemptAuthentication(request, response);
        }
    }
}

 然后对这个过滤器进行配置:

@EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userService;

    @Bean
    JsonLoginFilter jsonLoginFilter() {
        JsonLoginFilter filter = new JsonLoginFilter();
        filter.setAuthenticationSuccessHandler((req,resp,auth)->{
            resp.setContentType("application/json;charset=utf-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;charset=utf-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;
    }

    @Bean
    AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userService);
        ProviderManager pm = new ProviderManager(daoAuthenticationProvider);
        return pm;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //放行不需要拦截的接口资源
        http.authorizeRequests()
                .antMatchers(SystemConstants.HTTP_ACT_MATCHERS).permitAll()
                .anyRequest()
                .authenticated()

                //登录成功失败配置
                .and()
                .formLogin()
                        .and().addFilterAt(jsonLoginFilter(),UsernamePasswordAuthenticationFilter.class);

        //关闭csrf
        http.csrf().disable();
        //开启跨域
        http.cors();
        //关闭iframe请求
        http.headers().frameOptions().disable();
        //禁用缓存
        http.headers().cacheControl();
        //不适用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

 另外一种自定义 JSON 登录的方式是直接自定义登录接口,如下

@RestController
public class LoginController {

    @Autowired
    AuthenticationManager 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 中。

最后再配一下登录接口就行了:
 


@EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userService;

    @Bean
    AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userService);
        ProviderManager pm = new ProviderManager(daoAuthenticationProvider);
        return pm;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/doLogin").permitAll()
                .anyRequest()
                .authenticated();
    }

}

从 Spring Boot3(Spring Security6) 开始,上面这两种方案都出现了一些瑕疵。

具体表现就是:当你调用登录接口登录成功之后,再去访问系统中的其他页面,又会跳转回登录页面,说明访问登录之外的其他接口时,系统不知道你已经登录过了。

产生上面问题的原因,主要在于 Spring Security 过滤器链中有一个过滤器发生变化了:

在 Spring Boot3 之前,Spring Security 过滤器链中有一个名为 SecurityContextPersistenceFilter 的过滤器,这个过滤器在 Spring Boot2.7.x 中废弃了,但是还在使用,在 Spring Boot3 中则被从 Spring Security 过滤器链中移除了,取而代之的是一个名为 SecurityContextHolderFilter 的过滤器。
 

 先来看 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 {
	Supplier<SecurityContext> 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 就会认为当前用户没有登录。

这就是问题的原因!

找到原因,那么问题就好解决了。
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;charset=utf-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;charset=utf-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 方法设置一下就行。 

那么对于自定义登录接口的问题,解决思路也是类似的:

@RestController
public class LoginController {

    @Autowired
    AuthenticationManager 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 中了。 

5、动态权限管理

权限开发的思路,当我们设计好 RBAC 权限之后,具体到代码层面,我们有两种实现思路:

  1. 直接在接口 层方法上添加权限注解,这样做的好处是实现简单,但是有一个问题就是权限硬编码,每一个方法需要什么权限都是代码中配置好的,后期如果想通过管理页面修改是不可能的,要修改某一个方法所需要的权限只能改代码。

  2. 将请求和权限的关系通过数据库来描述,每一个请求需要什么权限都在数据库中配置好,当请求到达的时候,动态查询,然后判断权限是否满足,这样做的好处是比较灵活,只需数据库或者以后的系统的管理页面配置就行

旧方案实现动态权限验证是通过重写两个类来和实现,第一个类是收集权限元数据的类:

@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //...
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

在 getAttributes 方法中,根据当前请求的 URL 地址(从参数 Object 中可提取出来),然后根据权限表中的配置,分析出来当前请求需要哪些权限并返回。

另外我还重写了一个决策器,其实决策器也可以不重写,就看你自己的需求,如果 Spring Security 自带的决策器无法满足你的需求,那么可以自己写一个决策器:

@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        //...
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public 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 ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                    object.setAccessDecisionManager(customUrlDecisionManager);
                    object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                    return object;
                }
            })
            .and()
            //...
}

在Spring Security6 中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();
                List<MenuWithRoleVO> menuWithRole = menuService.getMenuWithRole();
                for (MenuWithRoleVO m : menuWithRole) {
                    if (antPathMatcher.match(m.getUrl(), requestURI)) {
                        isMatch = true;
                        //说明找到了请求的地址了
                        //这就是当前请求需要的角色
                        List<Role> 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); 即可。文章来源地址https://www.toymoban.com/news/detail-628731.html

到了这里,关于Spring Security6入门及自定义登录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Security6 最新版配置该怎么写,该如何实现动态权限管理

    Spring Security 在最近几个版本中配置的写法都有一些变化,很多常见的方法都废弃了,并且将在未来的 Spring Security7 中移除,因此又补充了一些新的内容,重新发一下,供各位使用 Spring Security 的小伙伴们参考。 接下来,我把从 Spring Security5.7 开始(对应 Spring Boot2.7 开始),各

    2024年02月12日
    浏览(45)
  • 在 Spring Security 中定义多种登录方式,比如邮箱、手机验证码登录

    现在各大网站登录的方式是越来越多。比如:传统的用户名密码登录、快捷的邮箱、手机验证码登录,还有流行的第三方登录。那本篇呢,就给大家带来如何在 Spring Security 中定义使用邮箱验证码登录方式。看完本篇,让你学会自定义认证方式,如果公司还需要使用手机验证

    2024年02月11日
    浏览(43)
  • 【深入浅出Spring Security(五)】自定义过滤器进行前后端登录认证

    在【深入浅出Spring Security(二)】Spring Security的实现原理 中小编阐述了默认加载的过滤器,里面有些过滤器有时并不能满足开发中的实际需求,这个时候就需要我们自定义过滤器,然后填入或者替换掉原先存在的过滤器。 首先阐述一下添加过滤器的四个方法(都是 HttpSecur

    2024年02月08日
    浏览(45)
  • 深入理解Spring Boot Starter:概念、特点、场景、原理及自定义starter

    在Spring框架的发展过程中,为了简化项目的搭建和配置过程,Spring Boot应运而生。Spring Boot通过提供一系列开箱即用的Starter,使得开发者能够快速整合Spring生态系统中的各种技术栈,提升开发效率。本文将深入探讨Spring Boot Starter的基本概念、主要特点、应用场景以及实现原理

    2024年02月22日
    浏览(46)
  • Spring Security--自动登录

    也就是remember me 在配置链上加一个  然后发送请求时加上:remember-me字段 value值可以为,ture,1,on 我们记住登录后,关掉浏览器再打开,访问一下接口,可以访问,说明记住登录成功了。    因为有的接口可以支持rememberMe认证,有的接口不支持,用上图的方式做区别。 //禁止

    2024年02月08日
    浏览(34)
  • Spring Security多登录页面示例

    在 Web 应用程序开发中,有两个单独的模块是很常见的 - 一个用于管理员用户,一个用于普通用户。每个模块都有一个单独的登录页面,并且可以与相同或不同的身份验证源相关联。换句话说,应用程序为不同类型的用户提供了多个登录页面:管理员和用户,或管理员和客户

    2023年04月23日
    浏览(31)
  • Spring Security进行登录认证和授权

    用户首次登录提交用户名和密码后spring security 的 UsernamePasswordAuthenticationFilter 把用户名密码封装 Authentication 对象 然后内部调用 ProvideManager 的 authenticate 方法进行认证,然后 ProvideManager 进一步通过内部调用 DaoAuthencationPriovider 的 authenticate 方法进行认证 DaoAuthencationPriovider 通过

    2024年02月11日
    浏览(43)
  • Spring-Security实现登录接口

    Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架 Shiro ,它提供了更丰富的功能,社区资源也比Shiro丰富。 具体介绍和入门看springSecurity入门 在实现之前幺要了解一下登录校验的流程以及 SpringSecurity 的原理以及认证流程 1、登录校验流程 2、 SpringSecurity完

    2024年01月18日
    浏览(50)
  • Spring Security登录用户数据获取(4)

    登录成功之后,在后续的业务逻辑中,开发者可能还需要获取登录成功的用户对象,如果不使用任何安全管理框架,那么可以将用户信息保存在HttpSession中,以后需要的时候直接从HttpSession中获取数据。在Spring Security中,用户登录信息本质上还是保存在 HttpSession中,但是为了方

    2024年02月03日
    浏览(49)
  • 系列十一、Spring Security登录接口兼容JSON格式登录

            前后端分离中,前端和后端的数据交互通常是JSON格式,而Spring Security的登录接口默认支持的是form-data或者x-www-form-urlencoded的,如下所示: 那么如何让Spring Security的登录接口也支持JSON格式登录呢?请看下文分析 

    2024年01月20日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包