1.Spring Security
2.Spring Security 原理
当我们仅仅只是添加Spring Security的依赖时,springboot会为我们做什么?做了两个事,首先就是把所有的服务的访问都保护起来了,访问资源的时候,他会定向到登陆页面
禁用:@SpringBootApplication(exclude = {SecurityAutoConfiguration.class })
Spring Security最核心的东西其实就是一组过滤器链,这些过滤器在启动的时候SpringBoot都会把它配置进去。
最主要的几种过滤器:处理用户的表单登陆的,处理异常的等等,处理表单的这个过滤器首先会检查这个请求是不是一个登陆的请求,其次检查当前的请求请求头会不会有过滤器需要的信息,比如说,当前这个请求里面带了用户名字和密码,那么这个过滤器就回去校验用户名字和密码是否正确,如果正确他就会放过去给下一个过滤器。任何一个过滤器认证成公了以后他会在请求上做一个标记,表明他这个过滤器认证成功。请求的最后一个过滤器FilterSecurityIntercepter,这个过滤器就会决定当前的请求会不会去访问后面的这个服务,他依据的就是代码里面的Security的配置。判断的结果就是过和不过,不过就会抛出异常。ExceptionTranslationFilter就是用来捕获后面这个过滤器抛出来的异常。
3.自定义用户认证逻辑
用户信息的获取逻辑在Spring Security里面封装在UserDetailsService 这个接口。
处理用户校验的逻辑的是UserDetails这个接口,用户自定义的User可以去实现这个接口,写里面的校验逻辑
处理加密解密 PasswordEncoder
4.个性化认证流程
首先有一个问题就是,在dem中o工程,扫不到core包的bean,所以就需要配置包扫描
因为我们项目中不可能用到security的默认登陆页面,所以我们要自定义登陆页面。
如果用户自定义了登陆页面,就跳到用户配置的登陆页面,如果没有那么跳到默认的登陆页面。
自定义配置文件
自定义登陆成功处理
默认登陆成功会后会跳到引发登陆的页面
自定义登陆处理器,登陆成功后返回Authentication的信息,登陆的方式不同,包含的信息也不同
@Component("wxAuthenticationSuccessHandler")
public class WxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//SpringMVC启动的时候会自动注册一个ObjectMapper
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
/**
* @param request
* @param response
* @param authentication
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
配置里面注册一下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin() //基于security默认表单登陆的方式
.loginPage("/authentication/require") //自定义登陆页面
.loginProcessingUrl("/authentication/form") //表单登陆提交的登陆请求地址
.successHandler(wxAuthenticationSuccessHandler) //自定义登陆成功后的处理
.permitAll()
.and()
.authorizeRequests() //下面这些配置
.antMatchers("/authentication/require").permitAll() //登陆页面不需要权限认证就可以访问
.antMatchers(securityProperties.getBrowser().getLoginPage()).permitAll() //用户自定义的登陆页面有也要授权访问
.anyRequest() //任何请求,都需要身份认证才能访问
.authenticated() 都需要认证
.and()
.csrf().disable();
}
失败的处理:
/**
*
*/
package com.wx.browser.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wx.browser.support.SimpleResponse;
import com.wx.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component("wxAuthenctiationFailureHandler")
public class WxAuthenctiationFailureHandler implements AuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(exception.getMessage())));
// if (LoginResponseType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
// response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
// response.setContentType("application/json;charset=UTF-8");
// response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(exception.getMessage())));
// }else{
// super.onAuthenticationFailure(request, response, exception);
// }
}
}
现在是返回json,但是我们要用户可以通过配置来实现是跳转到html,还是返回json:
定义枚举
在成功和失败的handler里面加上判断
/**
*
*/
package com.wx.browser.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wx.core.properties.LoginType;
import com.wx.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
//登陆成功后的处理
@Component("wxAuthenticationSuccessHandler")
public class WxAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//SpringMVC启动的时候会自动注册一个ObjectMapper
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
/**
* @param request
* @param response
* @param authentication
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
配置类:
public class BrowserProperties {
//如果用户没有自定义的登陆页面,跳转到标准登陆页面login.html
private String loginPage = "/standard-login.html";
//如果用户没有配置默认返回json
private LoginType loginType = LoginType.JSON;
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
public LoginType getLoginType() {
return loginType;
}
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
}
现在要跳转,返回html页面
失败跳转:
5.认证流程的源码详解
基于表单的认证
UsernamePasswordAuthenticationFilter:
AuthenticationManager的作用是用来管理 AuthenticationProvider,真正的校验逻辑是写在AuthenticationProvider,在这里面判断你是传入的token的类型,不同的过滤器传入的Token是不一样的,比如现在UsernamePasswordAuthenticationFilter传入的就是UsernamePasswordAuthenticationToken这个类型的Token,在AuthenticationProvider中就会去判断这个Token的类型,挑出一个Provider来处理。
在Provider中去掉了我我们提供的UserDetailsService接口的实现类的来获取我们的UserDetails,这里就掉了MyUserDetailsService类的loadUserByUsername方法。这儿调用的结果就是我们自己从数据库里面拿出来的我们自己的用户信息包装成的UserDetails。
如果没有拿到用户的信息会抛出异常,如果拿到了会做一个预检查,检查账户是不是被锁定是不是过期了之类的。
然后还有一个附加的检查,使用密码加密解密器presentedPassword,去校验当前的密码是否匹配。
所有的检查都通过以后,就认为用户的认证是成功的,我们就会拿用户的信息,以及传进来的认证请求的信息,来创建一个SuccessAuthentication,返回一个经过认证的Authentication。
认证的结果如何在多个请求之间共享
当请求经过SecurityContextPersistenceFilter时检查线程,如果线程里面有SecurityContext就拿出来放到session里面去,进来的时候检查session,有认证信息就放到session,出去的时候检查session,
获取认证用户的信息