SpringSecurity 应该是最常见的认证框架了 , 处于Spring体系中使他能快速的上手 , 这一篇开始作为入门级开篇作 , 来浅浅的讲一下SpringSecurity 的整体结构.
关于Security 的使用 , 官方文档已经太详细了, 建议直接查看 官方文档 , 作为开篇 , 不深入源码 , 只做体系的介绍~~
后续会陆陆续续将笔记中的源码梳理出来 ,笔记很乱, 但愿等整理出体系!
Security中需要我们操作的成员大概可以分为以下几种 , 但是涉及的类远远不止他们
JSON Web Signature (JWS)
JSON Web Encryption (JWE)
JSON Web Key (JWK)
SpringSecurity 中存在很多Filter , 抛开一些底层的 , 一般业务中的Filter主要是为了控制以何种方式进行认证 . 一般的体系结构里面 , 都会循环处理 , 例如 CAS 中 , 就是通过 HandlerManager 进行 for each 循环 , 而 SpirngSecurity 中 , 同样通过 SecurityFilterChain 进行循环.
SecurityFilterChain 在后期源码梳理的时候在详细介绍 , 这里先看张图 :
FilterChainProxy 使用 SecurityFilterChain 来确定应该为此请求调用哪个 Spring 安全过滤器 ,
FilterChainProxy 决定应该使用哪个 SecurityFilterChain。会调用第一个被匹配的SecurityFilterChain ,即匹配是有序的
已知的 Filter 类
ChannelProcessingFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth2LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter
OpenIDAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
SwitchUserFilter
认证体系是核心的处理体系 , 包含以下主要类 :
SecurityContextHolder : Spring Security 存储被验证者的详细信息的地方。
SecurityContext : 从 SecurityContextHolder 获得,并包含当前经过身份验证的用户的身份验证。。
Authentication : 可以作为 AuthenticationManager 的输入,以提供用户为身份验证或来自 SecurityContext 的当前用户提供的凭据。。
GrantedAuthority : 在身份验证上授予主体的权限(即角色、范围等)。
AuthenticationManager : 定义 Spring Security 的过滤器如何执行身份验证的 API。。
ProviderManager : AuthenticationManager 最常用的实现。。
Providationprovider : 由 ProviderManager 用于执行特定类型的身份验证。。
AuthenticationEntryPoint : 用于从客户机请求凭证(即重定向到登录页面,发送 www 认证响应等)。
AbstractAuthenticationProcessingFilter : 用作验证用户凭据的基本筛选器
AccessDecisionManager : 由 AbstractSecurityInterceptor 调用,负责做出最终的访问控制决策
后续我们会围绕以上类进行源码梳理:
以下是一个很简单的 Security 案例 : >>>>项目源码<<<<
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler myAuthenctiationFailureHandler;
@Bean
public UserService CustomerUserService() {
System.out.print("step1============");
return new UserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//BCryptPasswordEncoder().encode("123456")).roles("ADMIN");
auth.userDetailsService(CustomerUserService()).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//此方法中进行了请求授权,用来规定对哪些请求进行拦截
//其中:antMatchers--使用ant风格的路径匹配
//regexMatchers--使用正则表达式匹配
http.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/before/**").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/").permitAll()
.anyRequest().authenticated() //其它请求都需要校验才能访问
.and()
.formLogin()
.loginPage("/login") //定义登录的页面"/login",允许访问
.defaultSuccessUrl("/home") //登录成功后默认跳转到"list"
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
.logout() //默认的"/logout", 允许访问
.logoutSuccessUrl("/index")
.permitAll();
http.addFilterBefore(new BeforeFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/**/*.js", "/lang/*.json", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html", "/**/*.png");
}
}
其中有几个主要的地方 :
@EnableWebSecurity 干了什么 ?
TODO
public class UserService implements UserDetailsService {
//......
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = userRepository.findByUsername(username);
// ....
return user;
}
一个基本的 Demo 就完成了 , 案例能怎么简单 , 其实主要是因为我们复用了以下的类 :
我们把整个结构再定制一下 , 满足我们本身的功能 :
// 我们复用 UsernamePasswordAuthenticationFilter , 将其进行部分定制
public class DatabaseAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// 修改用户名为 account
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "account";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public DatabaseAuthenticationFilter() {
super(new AntPathRequestMatcher("/database/login", "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// username 从下方方法获取
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 核心 : 这里替换了 DatabaseUserToken
DatabaseUserToken authRequest = new DatabaseUserToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
protected void setDetails(HttpServletRequest request,
DatabaseUserToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return usernameParameter;
}
public final String getPasswordParameter() {
return passwordParameter;
}
}
Token 是在Authentication 中传递的核心 , 它用于后续进行票据的认证
public class DatabaseUserToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private String credentials;
private String type;
private Collection<? extends GrantedAuthority> authorities;
public DatabaseUserToken(Object principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.type = "common";
setAuthenticated(false);
}
public DatabaseUserToken(Object principal, String credentials, String type) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.type = StringUtils.isEmpty(type) ? "common" : type;
setAuthenticated(false);
}
public DatabaseUserToken(Object principal, String credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public DatabaseUserToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = null;
super.setAuthenticated(true); // must use super, as we override
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
/**
* @param isAuthenticated
* @throws IllegalArgumentException
*/
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
这里通过 supports 方法判断 token 是否符合 ,从而发起认证过程 (PS : 和 CAS 简直一个思路)
public class DatabaseAuthenticationProvider implements AuthenticationProvider {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserInfoService userInfoService;
@Autowired
private AntSSOConfiguration antSSOConfiguration;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.info("------> auth database <-------");
String username = (authentication.getPrincipal() == null)
? "NONE_PROVIDED" : String.valueOf(authentication.getPrincipal());
String password = (String) authentication.getCredentials();
if (StringUtils.isEmpty(password)) {
throw new BadCredentialsException("密码不能为空");
}
UserInfo user = userInfoService.searchUserInfo(new UserInfoSearchTO<String>(username));
logger.info("------> this is [{}] user :{}<-------", username, String.valueOf(user));
if (null == user) {
logger.error("E----> error :{} --user not fount ", username);
throw new BadCredentialsException("用户不存在");
}
String encodePwd = "";
if (password.length() != 32) {
encodePwd = PwdUtils.AESencode(password, AlgorithmConfig.getAlgorithmKey());
logger.info("------> {} encode password is :{} <-------", password, encodePwd);
}
if (!encodePwd.equals(user.getPassword())) {
logger.error("E----> user check error");
throw new BadCredentialsException("用户名或密码不正确");
} else {
logger.info("user check success");
}
DatabaseUserToken result = new DatabaseUserToken(
username,
new BCryptPasswordEncoder().encode(password),
listUserGrantedAuthorities(user.getUserid()));
result.setDetails(authentication.getDetails());
logger.info("------> auth database result :{} <-------", JSONObject.toJSONString(result));
return result;
}
@Override
public boolean supports(Class<?> authentication) {
return (DatabaseUserToken.class.isAssignableFrom(authentication));
}
private Set<GrantedAuthority> listUserGrantedAuthorities(String uid) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
if (StringUtils.isEmpty(uid)) {
return authorities;
}
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return authorities;
}
}
// 将 Provider 注入体系
auth.authenticationProvider(reflectionUtils.springClassLoad(item.getProvider()));
// 将 Filter 注入体系
AbstractAuthenticationProcessingFilter filter = reflectionUtils.classLoadReflect(item.getFilter());
filter.setAuthenticationManager(authenticationManager);
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
以上的流程可以看到已经不需要 继承 UserService类了 . 重写这些足够我们去实现大部分业务逻辑 , 使用时在Provider 完成对应的认证方式即可
Security 很好用, 在我的个人实践中 , 正在尝试将常见的协议进行整合 , 做成一个开源脚手架 , 个人感觉SpringSecuity 体系应该可以轻松的完成 .
开篇比较简单 , 正在构思怎样才能从实践的角度将他讲清楚 , 笔记也在陆陆续续整理 , 争取下个月将整套文章发出来 !
HttpSecurity 常用方法
阅读量:2022
点赞量:0
收藏量:0