共计 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已经起作用了,探究下是如何产生这个结果的。
3.spring security如何工作的?
看这种第三方库,当然是官方文档,废话不多说,上图
图中可以看到认证是由securityFilterChain开始执行的,那securityFilterChain又是如何被调用的呢?笔者不卖关子,且看笔者娓娓道来。我们知道springboot自动根据环境为我们装配了很多组件,spring security也不例外,首先@EnableWebSecurity自动启用
@EnableWebSecurity注解干了什么呢?
这里我们发现开启了WebSecurityConfiguration这个配置,查看WebSecurityConfiguration源码发现创建了个springSecurityFilterChain的bean
细心的小伙伴会发现websecurity是这么创建的,同时从beanFactory获取了所有WebSecurityConfigurer类型的bean(敲黑板!)
接下来websecurity就开始build了,
我们看到创建了securityFilterChain并委托给了一个FilterChainProxy实例然后返回这个FilterChainProxy,至此securityFilterChain如何被调用的总算明白了。securityFilterChain里面就是一堆filter,官方以AbstractAuthenticationProcessingFilter这个基类实现来说明如何鉴权的,
首先,尝试认证并返回Authentication,attemptAuthentication究竟干了什么呢?其实就是调用AuthenticationManager去认证去了,AuthenticationManager的通用实现就是ProviderManager,ProviderManager调用它的一堆AuthenticationProvider去干事去了(就像大领导叫小领导干事,小领导(当然不是说完全不干事 )分工给程序员们,程序员们就屁颠屁颠的去干活了)。补充下图
AuthenticationProvider开始干活
AuthenticationProvider执行真正的认证,并将认证结果返回,通常返回null或者发生异常说明认证失败了,除非定义了父认证管理器还能挽救一下。认证成功就会继续执行我们的请求了 ,认证失败就会出现文首类似的情况了。
这时候有小伙伴会问了,那先前的响应是怎么来的?当然是默认添加的!
瞅着没,当用户自己没定义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(?);
}
占位符需要小伙伴们自己探索
5.结束语
本文主要探讨了spring security的工作原理,并实现了自定义认证!