spring security with spring boot

1,572次阅读
没有评论

共计 6058 个字符,预计需要花费 16 分钟才能阅读完成。

1.什么是spring security?

引用官方原文:Spring Security is a powerful and highly customizable authentication and access-control framework,白话就是个牛逼的能够提供认证及访问权限的玩意。

2.如何集成?

2.1新建基于maven的boot应用不做细述,添加spring security依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.2添加测试接口

@Validated
@Tag(name = "书籍服务接口")
@RestController
@RequestMapping(value = "/api/book")
public class BookController {

    @Operation(tags = "书籍服务接口", summary = "获取所有书籍")
    @GetMapping(value = "/list")
    public CommonResult<?> selectAll() {
        return CommonResult.ok();
    }

}

2.3springboot配置文件调整

server.port=8085
server.servlet.context-path=/platform

2.4启动boot测试

mvn spring-boot:run

启动完毕后,控制台调用测试

curl localhost:8085/platform/api/book/list

亦或者浏览器访问,结果如下
spring security with spring boot

spring security with spring boot

看到这个结果,不要慌,至少看来spring security已经起作用了,探究下是如何产生这个结果的。

3.spring security如何工作的?

看这种第三方库,当然是官方文档,废话不多说,上图

spring security with spring boot

图中可以看到认证是由securityFilterChain开始执行的,那securityFilterChain又是如何被调用的呢?笔者不卖关子,且看笔者娓娓道来。我们知道springboot自动根据环境为我们装配了很多组件,spring security也不例外,首先@EnableWebSecurity自动启用spring security with spring boot

@EnableWebSecurity注解干了什么呢?

spring security with spring boot

这里我们发现开启了WebSecurityConfiguration这个配置,查看WebSecurityConfiguration源码发现创建了个springSecurityFilterChain的bean

spring security with spring boot

细心的小伙伴会发现websecurity是这么创建的,同时从beanFactory获取了所有WebSecurityConfigurer类型的bean(敲黑板!)

spring security with spring boot

接下来websecurity就开始build了,

spring security with spring boot

我们看到创建了securityFilterChain并委托给了一个FilterChainProxy实例然后返回这个FilterChainProxy,至此securityFilterChain如何被调用的总算明白了。securityFilterChain里面就是一堆filter,官方以AbstractAuthenticationProcessingFilter这个基类实现来说明如何鉴权的,

spring security with spring boot

首先,尝试认证并返回Authentication,attemptAuthentication究竟干了什么呢?其实就是调用AuthenticationManager去认证去了,AuthenticationManager的通用实现就是ProviderManager,ProviderManager调用它的一堆AuthenticationProvider去干事去了(就像大领导叫小领导干事,小领导(当然不是说完全不干事 spring security with spring boot )分工给程序员们,程序员们就屁颠屁颠的去干活了)。补充下图

spring security with spring boot

AuthenticationProvider开始干活

spring security with spring boot

AuthenticationProvider执行真正的认证,并将认证结果返回,通常返回null或者发生异常说明认证失败了,除非定义了父认证管理器还能挽救一下。认证成功就会继续执行我们的请求了 spring security with spring boot ,认证失败就会出现文首类似的情况了。

这时候有小伙伴会问了,那先前的响应是怎么来的?当然是默认添加的!

spring security with spring boot

瞅着没,当用户自己没定义WebSecurityConfigurerAdapter类型的bean时,servlet环境下就会创建一个默认的。于是乎就有了默认的认证失败行为。

4.自定义认证

要认证自然实现AbstractAuthenticationProcessingFilter,这个类目前只有一个实现UsernamePasswordAuthenticationFilter,照葫芦画瓢,笔者的实现,仅供参考

4.1创建filter

public class ApiFilter extends AbstractAuthenticationProcessingFilter {
    private static final Logger logger = LoggerFactory.getLogger(ApiFilter.class);

    public ApiFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher("/**/api/**"));
        this.setAuthenticationManager(authenticationManager);
        this.setAuthenticationDetailsSource(new WebAuthenticationDetailsSource());
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        ApiAuthenticationToken apiAuthenticationRequest = new ApiAuthenticationToken();
        setDetails(request, apiAuthenticationRequest);
        return this.getAuthenticationManager().authenticate(apiAuthenticationRequest);
    }

    protected void setDetails(HttpServletRequest request,
                              ApiAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        authRequest.setRequest(request);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request, response);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Authentication request failed: " + failed.toString(), failed);
            logger.debug("Updated SecurityContextHolder to contain null Authentication");
        }
        SecurityContextHolder.clearContext();
        AuthenticationFailureHandler failureHandler = new AuthenticationEntryPointFailureHandler(new ?());
        failureHandler.onAuthenticationFailure(request, response, failed);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        super.doFilter(new ?(request), res, chain);
    }
}
public class ApiAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = 1535207260924210027L;
    private HttpServletRequest request;

    public ApiAuthenticationToken() {
        this(Collections.emptyList());
        setAuthenticated(false);
    }

    /**
     * Creates a token with the supplied array of authorities.
     *
     * @param authorities the collection of <tt>GrantedAuthority</tt>s for the principal
     *                    represented by this authentication object.
     */
    public ApiAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    public void setRequest(HttpServletRequest request) {
        this.request = request;
    }
}

4.2创建AuthenticationProvider

实现AuthenticationProvider,支持的认证类型

@Override
public boolean supports(Class<?> authentication) {
    return ApiAuthenticationToken.class.isAssignableFrom(authentication);
}

具体认证逻辑

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// just prevent call directly
if (!supports(authentication.getClass())) {
return null;
}
// todo:认证
// 成功返回
return apiAuthenticationToken;
}

4.3创建WebSecurityConfigurerAdapter

重点就重写2个方法

@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(apiAuthenticationProvider);
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors();
httpSecurity.csrf()
.disable();
httpSecurity.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.antMatcher("/**/api/**")
.authorizeRequests()
.anyRequest().authenticated();
httpSecurity.addFilterBefore(new ApiFilter(authenticationManagerBean()), BasicAuthenticationFilter.class);
httpSecurity.exceptionHandling()
.authenticationEntryPoint(?)
.accessDeniedHandler(?);
}

占位符需要小伙伴们自己探索 spring security with spring boot

5.结束语

本文主要探讨了spring security的工作原理,并实现了自定义认证!

正文完
 1
mysteriousman
版权声明:本站原创文章,由 mysteriousman 2021-11-28发表,共计6058字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)