1. 表单认证
1.1.Pom依赖
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
1.2.实现SpingSecurity的安全适配器类
实现接口:WebSecurityConfigurerAdapter
简单实现configure方法:
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()//表单登录方式
.loginPage(url)//执行登录跳转的controller的URL
.loginProcessingUrl(url)//验证登录URL
.successHandler(class)//表单登录成功以后的响应
.failureHandler(class)//表单登录失败以后的响应
.and()
.authorizeRequests()//权限控制
.antMatchers(url,url).permitAll()//无需权限可访问的url
.anyRequest().authenticated()//其余访问皆需要权限
.and()
.csrf().disable()//关闭csrf攻击防护
;
}
1.3.处理用户信息校验逻辑
实现接口:
UserDetailsService:获取用户信息逻辑处理
UserDetails:处理用户逻辑
PasswordEncoder:处理用户密码加密逻辑
public class CustomUserDeatilsService implements UserDetailsService{
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 处理用户信息校验逻辑
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名得到用户信息,返回给userDetails接口的实例化user对象
// AuthorityUtils.commaSeparatedStringToAuthorityList字符串转换权限collection对象
//passwordEncoder.encode:该方法在注册时使用,实际上直接在数据库中拿出密码匹配
String password = passwordEncoder.encode("123456");
return new User(username,password, true,true,true,true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
1.4.认证成功与失败处理逻辑
实现接口:
AuthenticationFailureHandler
AuthenticationSuccessHandler
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//实现跳转登录页显示响应信息
super.onAuthenticationFailure(request,response,exception);
}
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
@Autowired
private ObjectMapper objectMapper;
//响应处理方式为json格式,返回json
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//将authentication对象转换成json格式的字符串,写回响应里
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
2.SpringSecurity原理解析
2.1.基本原理
2.1.1.过滤器基本原理图
过滤器机制
2.1.2.SpringSecurity基本原理图
SpringSecurity实质是一组过滤器链的集合。
2.1.3.过滤器作用
- SecurityContextPersistenceFilter:用于设置SecurityContext到SecurityContextHolder中,以保证获取的认证结果在多个请求之间共享
- UsernamePasswordAuthenticationFilter:对于表单登陆进行验证的处理逻辑
- ExceptionTranslationFilter:解决在处理一个请求时产生的指定异常
- FilterSecurityInterceptor:校验登陆信息的权限校验
2.1.4.大体认证流程
- 当用户发送request请求,首先经过SecurityContextPersistenceFiltersecurity过滤器,Context从SecurityContextRepository创建一个securityContext给以后的过滤器使用
- 各类认证过滤器根据各自认证逻辑对用户进行认证处理,此时用户请求被保存,跳转到指定过滤器的请求上,例如UsernamePasswordAuthenticationFilter对表单登录逻辑进行认证处理(request="/login",method="POST")
- 在FilterSecurityInterceptor进行用户识别和权限的校验,认证失败,交由ExceptionTranslationFilter根据指定的异常进行处理。
- 认证成功,则重定向到用户请求,访问指定的资源
2.2.以表单认证逻辑演示springsecurity的认证处理逻辑
2.2.1.主要涉及组建
接口 | 实现类(接口) | 方法 | 作用描述 |
---|---|---|---|
AbstractAuthenticationProcessingFilter | UsernamePasswordAuthenticationFilter | attemptAuthentication(HttpServletRequest request,HttpServletResponse response) | 接收用户名密码并创建响应token,组装Authentication |
AuthenticationManager | ProviderManager | authenticate(Authentication authentication) | 对认证进行管理,并分发给指定的provider类校验认证信息 |
AuthenticationProvider(AbstractUserDetailsAuthenticationProvider) | DaoAuthenticationProvider | 1.authenticate(Authentication authentication) 2.additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) 3.createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) | 校验用户的凭证信息,失败会抛出一个特定的异常,成功则重新填充一个指定的token(UsernamePasswordAuthenticationToken);三个方法作用:1.校验逻辑处理2.校验密码是否匹配3.检验成功组装一个新的token |
UserDetailsChecker | DefaultPreAuthenticationChecks和DefaultPostAuthenticationChecks(在AbstractUserDetailsAuthenticationProvider抽象类中的私有类) | check(UserDetails user) | 1.预检查(账户是否锁定,账户是否过期,该账户是否存在)2.后检查(密码是否过期) |
AbstractAuthenticationToken(Authentication) | UsernamePasswordAuthenticationToken | - | Authentication对象,保存用户的详细信息,用于spring security执行逻辑使用 |
UserDetailsService | CustomUserDetailsService(自己实现) | loadUserByUsername(String username) | 获取用户信息 |
UserDetails | user() | - | 封装用户信息的实体类,存储从后台获取的用户信息 |
2.2.2.逻辑流程图
2.2.3.代码解析
-
表单形式登录,发送request请求
-
当spring security执行时发现该请求用户并未通过认证,会将request请求引导到AbstractAuthenticationProcessingFilter中的request(url="/login",method=POST)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
.... ....
Authentication authResult;
try {
// 用户名密码验证处理逻辑,最后获取一个Authentication
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// 认证失败处理逻辑
unsuccessfulAuthentication(request, response, failed);
return;
}
.... ....
//认证成功处理逻辑
successfulAuthentication(request, response, chain, authResult);
}
此过滤器执行doFilter()处理逻辑,主要执行三个主要方法
①. attemptAuthentication(request, response);
②. unsuccessfulAuthentication(request, response, failed)
③. successfulAuthentication(request, response, chain, authResult)
①. attemptAuthentication(request, response)
由其实现类UsernamePasswordAuthenticationFilter的 attemptAuthentication()方法实现
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//将用户名密码封装成Token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
//将token传递给AuthenticationManager执行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
在该类中attemptAuthentication方法主要将请求的用户名密码封装成UsernamePasswordAuthenticationToken对象,并交给AuthenticationManager处理认证逻辑;
AuthenticationManager管理着不同认证的处理逻辑,相当于一个认证的管理者,不同认证需要不同的处理逻辑,而由其实现类ProviderManager来分配具体的认证验证逻辑。
②. unsuccessfulAuthentication(request, response, failed)
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
//清空保存在securitycontext中的用户数据
SecurityContextHolder.clearContext();
.... ...
rememberMeServices.loginFail(request, response);
//失败的逻辑处理
failureHandler.onAuthenticationFailure(request, response, failed);
}
此方法主要完成了两个动作,1.认证失败后将securitycontext中的用户信息删除。2.将错误信息交给接口AuthenticationFailureHandler来完成对浏览器的响应,我们可以继承该接口及其实现类来完成对响应的操作。
③. successfulAuthentication(request, response, chain, authResult)
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//将认证成功的用户信息Authention保存到线程级的SecurityContextHolder的SecurityContext中,以便后面过滤器使用
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
.... .....
successHandler.onAuthenticationSuccess(request, response, authResult);
}
此方法主要完成了两个动作,1.将认证成功的Authention保存下来,并写入securitycontext中去。2.将Authention交给接口AuthenticationSuccessHandler来完成对浏览器的响应,可以继承该接口及其实现类来完成对响应的操作。
-
封装成UsernamePasswordAuthenticationToken的用户信息交给AuthenticationManager来执行具体用户信息校验,由其实现类ProviderManager来具体实现
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
.... ....
Authentication result = null;
.... ....
//遍历所有的AuthenticationProvider的实现,找到其实现类中可以解析Authenticaion用户信息的provider
for (AuthenticationProvider provider : getProviders()) {
.... ....
try {
//具体执行方法
result = provider.authenticate(authentication);
}
.... ....
if (result == null && parent != null) {
try {
result = parent.authenticate(authentication);
}
.... .....
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
}
在该方法中主要遍历接口AuthenticationProvider的所有实现类,找到能够解析Authentication的逻辑,并进行用户信息校验。在该案例中,主要由抽象类AbstractUserDetailsAuthenticationProvider以及其实现类DaoAuthenticationProvider完成验证
-
当ProviderManager遍历出了合适的处理逻辑,即将Authentication传递给它进行具体的逻辑校验,本案例由AbstractUserDetailsAuthenticationProvider完成
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
.... .....
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
try {
//该方法查询后台,获取用户信息(例.数据库中存放的)
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
.... ....
}
try {
//预检查:检查账户是否锁定,过期和存在
preAuthenticationChecks.check(user);
//附加检查:密码是否匹配
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
.... ....
//后检查:密码是否过期
postAuthenticationChecks.check(user);
return createSuccessAuthentication(principalToReturn, authentication, user);
}
该provider主要在authenticate执行验证,设计四个具体流程方法。
1.获取用户信息UserDetails
①. retrieveUser(username,authentication)
2.对用户信息UserDetails进行校验
②. preAuthenticationChecks.check(user)
③. additionalAuthenticationChecks(user,authentication)
④. postAuthenticationChecks.check(user)
①. retrieveUser(username,authentication)
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
//根据username向后台请求用户信息
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
.... ....
return loadedUser;
}
该方法在DaoAuthenticationProvider中具体执行,通过UserDetailsService类执行获取用户信息逻辑,并封装成UserDetails接口的实现类。该方法由编程人员实现。
②. preAuthenticationChecks.check(user)
private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
//账户是否被锁定
if (!user.isAccountNonLocked()) {
logger.debug("User account is locked");
throw new LockedException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
//账户是否存在
if (!user.isEnabled()) {
logger.debug("User account is disabled");
throw new DisabledException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.disabled",
"User is disabled"));
}
//账户上是否过期
if (!user.isAccountNonExpired()) {
logger.debug("User account is expired");
throw new AccountExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.expired",
"User account has expired"));
}
}
}
该方法在AbstractUserDetailsAuthenticationProvider抽象类中的私有类中实现。主要判断账户是否被锁定,过期和存在。
③. additionalAuthenticationChecks(user,authentication)
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
presentedPassword, salt)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
该方法在DaoAuthenticationProvider中实现,主要验证匹配密码
④. postAuthenticationChecks.check(user)
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired",
"User credentials have expired"));
}
}
}
该方法在AbstractUserDetailsAuthenticationProvider抽象类中的私有类中实现。主要判断密码是否过期。
由以上流程中可以看出我们可实现部分
①实现UserDetailsService以及UserDetails接口,完成获取并验证用户信息的逻辑
②实现AuthenticationFailureHandler接口,完成登陆失败的逻辑处理
③实现AuthenticationSuccessHandler接口,完后登录成功的逻辑处理
2.3.Authentication解析以及实现在多个请求中共享
2.1.Authentication
Authentication存储了用户的详细信息,包括唯一标识(如用户名)、凭证信息(如密码)以及本用户被授予的一个或多个权限.开发人员通常会使用Authentication对象来获取用户的详细信息,或者使用自定义的认证实现以便在Authentication对象中增加应用依赖的额外信息
Authentication接口可以实现的方法:
方法签名 | 描述 |
---|---|
Object getPrincipal() | 返回安全实体的唯一标识(如,一个用户名) |
Object getCredentials() | 返回安全实体的凭证信息 |
List<GrantedAuthority>getAuthorities() | 得到安全实体的权限集合,根据认证信息的存储决定的 |
Object getDetails() | 返回一个跟认证提供者相关的安全实体细节信息 |
2.2.SecurityContext
参考:
- Spring Security 认证系统浅析:http://z276356445t.iteye.com/blog/2372689
- Spring Security 的过滤器机制:Spring Security 的过滤器机制
- Spring Security技术栈开发企业级认证与授权