千家信息网

如何使用SpringSecurity扩展与配置

发表于:2024-10-22 作者:千家信息网编辑
千家信息网最后更新 2024年10月22日,本篇内容主要讲解"如何使用SpringSecurity扩展与配置",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"如何使用SpringSecurity扩展与配
千家信息网最后更新 2024年10月22日如何使用SpringSecurity扩展与配置

本篇内容主要讲解"如何使用SpringSecurity扩展与配置",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"如何使用SpringSecurity扩展与配置"吧!

简介

SpringSecurity原理(一)--初探 SpringSecurity原理(二)--认证 SpringSecurity原理(三)--授权 SpringSecurity原理(四)--过滤器 SpringSecurity原理(五)--扩展与配置

自定义扩展

自定义Filter

自定义Filter应该是最常用的需求了,例如,为了拦截大多数的暴力登录,我们一般会在登录的时候给一个验证码,但是UsernamePasswordAuthenticationFilter没有提供验证码的校验,所以我们就可以自定义一个Filter来处理验证码。

又如,对于前后端分离项目,我们更多使用Token,而不是Session,也可以通过Filter来处理Token的校验,续期的问题。

自定义Filter的方式有很多:

  1. 直接实现Filter

  2. 继承GenericFilterBean

  3. 继承OncePerRequestFilter重写doFilterInternal

  4. 继承BasicAuthenticationFilter重写doFilterInternal

  5. 继承AbstractAuthenticationProcessingFilter重写attemptAuthentication

  6. 继承UsernamePasswordAuthenticationFilter重写attemptAuthentication

  7. ……

后3个都是认证相关的Filter。

因为涉及到转发重定义等问题,一次请求Filter可能不止一次被调用,OncePerRequestFilter就是为了解决这个问题,它保证一次请求继承它的Filter只会被调用一次。

BasicAuthenticationFilter本身继承了OncePerRequestFilter,所以不用自己处理因为转发等引起的Filter多次调用问题。

AbstractAuthenticationProcessingFilter添加了认证失败,认证成功等处理,但是它没有处理一次请求可能多次调用的问题。

对于表单认证,想偷懒,可以自己继承UsernamePasswordAuthenticationFilter,例如,继承UsernamePasswordAuthenticationFilter,先处理验证码问题,如果校验成功,再调用UsernamePasswordAuthenticationFilter的attemptAuthentication方法。

反正自定义Filter非常灵活,根据自己的喜好选择。

自定义了Filter如何配置呢?

最简单的方式,自定义配置类重写WebSecurityConfigurerAdapter的configure方法:

 @Overrideprotected void configure(HttpSecurity http) {    http.addFilter(zzzFilter)            .addFilterAfter(aaaFilter)            .addFilterBefore(yyyFilter, UsernamePasswordAuthenticationFilter.class)            .addFilterAt(xxxFilter,UsernamePasswordAuthenticationFilter.class);}
  1. addFilter是添加到最后,但并不是最终的最后,因为后面的流程还会添加其他Filter

  2. addFilterAfter,添加在指定Filter之后

  3. addFilterBefore,添加在指定Filter之前

  4. addFilterAt,添加在指定Filter之前,不会覆盖和删除指定的Filter,感觉和addFilterBefore差不多

当然,也可以通过SecurityConfigurerAdapter的方式:

public class JwtConfigurer extends SecurityConfigurerAdapter {    @Override    public void configure(HttpSecurity http) {        JwtAuthenticationFilter filter = new JwtAuthenticationFilter();        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);    }}
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {      @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests((requests) -> requests.anyRequest().authenticated());        http.formLogin();        http.httpBasic();        http.apply(new JwtConfigurer());    }}

自定义登出成功处理器

实现LogoutSuccessHandler接口,一般返回json数据,以便于前端给出提示信息。

public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {        @Override        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {                if (authentication != null) {                        new SecurityContextLogoutHandler().logout(request, response, authentication);                }                response.setContentType("application/json;charset=UTF-8");                ServletOutputStream outputStream = response.getOutputStream();                outputStream.write("jwt loginout success").getBytes("UTF-8"));                outputStream.flush();                outputStream.close();        }}

配置方式:

protected void configure(HttpSecurity http){    http..logout()                                .logoutSuccessHandler(new JwtLogoutSuccessHandler());}

认证失败处理器

实现AuthenticationFailureHandler接口,一般会返回json数据,然后前端根据返回数据,自己决定提示信息和跳转。

public class LoginFailureHandler implements AuthenticationFailureHandler {        @Override        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {                response.setContentType("application/json;charset=UTF-8");                ServletOutputStream outputStream = response.getOutputStream();                outputStream.write(JSONUtil.toJsonStr("登录失败").getBytes("UTF-8"));                outputStream.flush();                outputStream.close();        }}

AuthenticationSuccessHandler

实现AuthenticationSuccessHandler接口,如果有生成token的逻辑可以放在这里面。

public class LoginSuccessHandler implements AuthenticationSuccessHandler {        @Override        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {                response.setContentType("application/json;charset=UTF-8");                ServletOutputStream outputStream = response.getOutputStream();                // 生成保存jwt等逻辑可以放这里                outputStream.write(JSONUtil.toJsonStr("登录成功").getBytes("UTF-8"));                outputStream.flush();                outputStream.close();        }}

当然,也可以通过继承SimpleUrlAuthenticationSuccessHandler的方式。

配置也是老方式:

@Overrideprotected void configure(HttpSecurity http) throws Exception {                http.formLogin()                                .successHandler(loginSuccessHandler)                                .failureHandler(loginFailureHandler)}

认证异常跳转入口

实现AuthenticationEntryPoint接口,这个接口在ExceptionTranslationFilter这个Filter截取到认证异常之后被调用,一般就是跳转登录页,可以参考:LoginUrlAuthenticationEntryPoint

public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {        @Override        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {                response.setContentType("application/json;charset=UTF-8");                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);                ServletOutputStream outputStream = response.getOutputStream();                outputStream.write(JSONUtil.toJsonStr("用户名或者密码错误").getBytes("UTF-8"));                outputStream.flush();                outputStream.close();        }}

授权异常处理器

实现AccessDeniedHandler接口,这个接口在ExceptionTranslationFilter截获到的授权异常之后被调用。

public class JwtAccessDeniedHandler implements AccessDeniedHandler {        @Override        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {                response.setContentType("application/json;charset=UTF-8");                response.setStatus(HttpServletResponse.SC_FORBIDDEN);                ServletOutputStream outputStream = response.getOutputStream();                outputStream.write(JSONUtil.toJsonStr("您没有操作权限,请联系管理员").getBytes("UTF-8"));                outputStream.flush();                outputStream.close();        }}

自定义认证凭证

可以实现Authentication,也可以继承AbstractAuthenticationToken。一般不需要,除非要自定义认证器。

import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.core.GrantedAuthority;import java.util.Collection;public class JwtAuthenticationToken extends AbstractAuthenticationToken {    public JwtAuthenticationToken(Collection authorities) {        super(authorities);    }    @Override    public Object getCredentials() {        return null;    }    @Override    public Object getPrincipal() {        return null;    }}

自定义认证器

实现AuthenticationProvider接口。

import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;public class JwtAuthenticationProvider implements AuthenticationProvider {    @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        return null;//认证流程    }    @Override    public boolean supports(Class authentication) {//支持校验哪种认证凭证        return authentication.isAssignableFrom(JwtAuthenticationToken.class);    }}

自定义投票者

可以实现AccessDecisionVoter接口,也可以直接继承WebExpressionVoter之类的,看具体需求,一般不需要,除非要自己设计新的授权体系。

public class MyExpressionVoter extends WebExpressionVoter {    @Override    public int vote(Authentication authentication, FilterInvocation fi, Collection attributes) {        return 1 ;//-1反对,0弃权,1同意    }}

配置

配置WebSecurity

一般配置WebSecurity,主要是为了忽略静态资源校验。

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    public void configure(WebSecurity web) {        web.ignoring().antMatchers(                "/**/*.html",                "/public/**/*.js",                "/public/**/*.css",                "/public/**/*.png",                "/**/*.gif", "/**/*.png", "/**/*.jpg", "/**/*.ico");    }}

匹配规则使用的是:AntPathRequestMatcher

配置HttpSecurity

HttpSecurity可以配置的东西太多了,下面是一个示例,可以参考,注意很多是重复不必要的配置,只是为了展示可以配置的内容。

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {    public AccessDecisionManager accessDecisionManager(){        List> decisionVoters                = Arrays.asList(                new WebExpressionVoter(),                new RoleVoter(),                new AuthenticatedVoter());        return new ConsensusBased(decisionVoters);    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http.                // 配置登录页,登录用户名密码参数                formLogin().loginPage("/login")                .passwordParameter("username").passwordParameter("password")                // 配置登录接口,defaultSuccessUrl配置登录成功跳转,会跳转到来的路径,而successForwardUrl会跳转固定页面                .loginProcessingUrl("/do-login").defaultSuccessUrl("/loginsucess")                // 登录失败处理                .failureForwardUrl("/fail").failureUrl("/failure").failureHandler(loginAuthenticationFailureHandler)                .permitAll()                // 配置特定url权限                .and().authorizeRequests().antMatchers("/admin").hasRole("admin")                // 配置鉴权管理器                .anyRequest().authenticated().accessDecisionManager(accessDecisionManager())//               配置登出,登录url,登出成功处理器                .and().logout().logoutUrl("/logout").logoutSuccessHandler(new JwtLogoutSuccessHandler())//               关闭csrf                .and().csrf().disable();//      配置自定义过滤器        http.addFilterAt(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);//        配置鉴权失败的处理器        http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());    }}

几个重要的类与接口

SecurityBuilder

public interface SecurityBuilder {        O build() throws Exception;}

就是构建一个特殊对象O的抽象,例如Filter。

WebSecurity就是一个为了创建Filter对象而设计。

HttpSecurity也是一个SecurityBuilder,不过它是为了创建DefaultSecurityFilterChain。

SecurityConfigurer

public interface SecurityConfigurer> {        void init(B builder) throws Exception;        void configure(B builder) throws Exception;}

SecurityConfigurer目的主要有两个:

  1. init,初始化builder,例如给一些filter设置参数

  2. configure,配置builder,例如创建添加新的filter

SecurityConfigurerAdapter

SecurityConfigurer的适配器,提供了init,configure的空实现,并且添加了一个CompositeObjectPostProcessor后置处理器,主要是用来处理Filter。

AbstractHttpConfigurer

最主要添加了一个disable方法,基本上很多Filter的configure类都继承了它。

WebSecurityConfigurer

public interface WebSecurityConfigurer> extends SecurityConfigurer {}

WebSecurityConfigurer主要是实例化了类型参数,告诉大家,我就是配置生产Filter的的SecurityBuilder。

WebSecurityConfigurerAdapter

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer {}

WebSecurityConfigurerAdapter更进一步告诉大家,我的SecurityBuilder就是WebSecurity,生产的就是Filter。

当然WebSecurityConfigurerAdapter没有开空头支票,还提供了很多功能,我们自己要对SpringSecurity进行配置的话,基本都是继承WebSecurityConfigurerAdapter。

WebSecurity

WebSecurity是一个SecurityBuilder,所以它的主要职责之一就是创建Filter,重点关注它的build方法,是继承了AbstractSecurityBuilder的build,具体逻辑在AbstractConfiguredSecurityBuilder的doBuild方法中。

@Overrideprotected final O doBuild() throws Exception {    synchronized (this.configurers) {        this.buildState = BuildState.INITIALIZING;        beforeInit();        init();        this.buildState = BuildState.CONFIGURING;        beforeConfigure();        configure();        this.buildState = BuildState.BUILDING;        O result = performBuild();        this.buildState = BuildState.BUILT;        return result;    }}

很标准的模板方法模式。

正真执行构建的逻辑在performBuild方法中,这个方法在WebSecurity创建了FilterChainProxy,在HttpSecurity中创建了DefaultSecurityFilterChain。

HttpSecurity

前面已经分析了,HttpSecurity的目的就一个创建DefaultSecurityFilterChain,注意它的performBuild方法。

到此,相信大家对"如何使用SpringSecurity扩展与配置"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

0