SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认是使用ThreadLocal实现的,这样就保证了本线程内所有的方法都可以获得SecurityContext对象。
可以通此方法过来获取当前操作用户信息:
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
默认返回的对象是UserDetails实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。比如下面只对token进行操作就可以吧token作为属性放入UserDetails实现类中。
Authentication是Spring Security方式的认证主体。
<1> Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是最高级别的身份/认证的抽象。
<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
authentication.getPrincipal()返回了一个Object,我们将Principal强转成了Spring Security中最常用的UserDetails,这在Spring Security中非常常见,接口返回Object,使用instanceof判断类型,强转成对应的具体实现类。接口详细解读如下:
AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中身份认证的方式有多种,一般不使用AuthenticationManager,而是使用AuthenticationManager的实现类ProviderManager ,ProviderManager内部会维护一个List
总结:
SecurityContextHolder:存放身份信息的容器
Authentication:用户信息的抽象
AuthenticationManager:身份认证器
1、通过过滤器过滤到用户请求的接口,获取身份信息(假如有多个认证方式会配置provider的顺序)
2、一般将身份信息封装到封装成Authentication下的实现类UsernamePasswordAuthenticationToken中
3、通过AuthenticationManager 身份管理器(通过配置找到对应的provider)负责验证这个UsernamePasswordAuthenticationToken
4、认证成功后(认证逻辑一般在service中),AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
5、SecurityContextHolder安全上下文容器将第2步填充了信息的UsernamePasswordAuthenticationToken,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中来建立安全上下文(security context)。
拦截api/的所有接口进行验证,验证token用户与id用户是否一致,不一致或token过期则没有权限访问
1、添加security相关依赖:spring-boot-starter-security spring-security-oauth2
2、全局配置类,根据不同需求配置不同的过滤器和provider(代码片段)
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SecurityOrgPeopleMapper securityOrgPeopleMapper;
@Autowired
private ImCheckTokenFactory imCheckTokenFactory;
// oauth2 server
@Override
protected void configure(AuthenticationManagerBuilder auth) {
addProvider(auth);
}
//指定provider
private void addProvider(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(imAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 请求过滤 对api/对所有接口都验证
http
.authorizeRequests()
.antMatchers("/api/**").access("@permissionChecker.hasPermission(authentication,request)")
.anyRequest().authenticated();
registerFilter(http);
}
//指定filter过滤器
private void registerFilter(HttpSecurity http) throws Exception {
http
.addFilterBefore(new ImAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
private ImAuthenticationProvider imAuthenticationProvider() {
return new ImAuthenticationProvider(imCheckTokenFactory, securityOrgPeopleMapper);
}
}
过滤器:
public class ImAuthenticationFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
public ImAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 其他过滤器已经认证通过了
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = asHttp(request);
HttpServletResponse httpResponse = asHttp(response);
//获取接口中都用户信息
String userId = obtainUserId(httpRequest);
String token = obtainToken(httpRequest);
String client = obtainClient(httpRequest);
try {
checkToken(token);
imProcessTokenAuthentication(Integer.parseInt(userId), token, client);
chain.doFilter(request, response);
} catch (UserAuthenticationException userAuthenticationException) {
logger.warn(userAuthenticationException.getMessage());
httpResponse.setStatus(userAuthenticationException.getStatus());
} catch (AuthenticationException authenticationException) {
chain.doFilter(request, response);
}
}
private String obtainToken(HttpServletRequest request) {
String tokenParameter = "F-Session";
String token = request.getHeader(tokenParameter);
if (Objects.isNull(token)) {
token = request.getParameter(tokenParameter);
}
return token;
}
private String obtainUserId(HttpServletRequest request) {
String userIdParameter = "userId";
return request.getParameter(userIdParameter);
}
private String obtainClient(HttpServletRequest request) {
String clientParameter = "client";
return request.getParameter(clientParameter);
}
private HttpServletRequest asHttp(ServletRequest request) {
return (HttpServletRequest) request;
}
private HttpServletResponse asHttp(ServletResponse response) {
return (HttpServletResponse) response;
}
private void checkToken(String token) {
if (StringUtils.isEmpty(token)) {
throw new UserAuthenticationException(SecurityHttpServletResponse.TOKEN_INVALID, "authenticate.fail");
}
}
//im//将用户信息封装到ImTokenAuthentication(自定义用户信息类)中
private void imProcessTokenAuthentication(Integer userId, String token, String client) {
Authentication resultOfAuthentication = imTryToAuthenticateWithToken(userId, token, client);
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}
private Authentication imTryToAuthenticateWithToken(Integer userId, String token, String client) {
ImTokenAuthentication imTokenAuthentication = new ImTokenAuthentication(userId, token, client);
return tryToAuthenticate(imTokenAuthentication);
}
private Authentication tryToAuthenticate(Authentication requestAuthentication) throws AuthenticationException {
//找到配置的authenticationManager实现类provider进行验证返回充满信息的Authentication
Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
throw new InternalAuthenticationServiceException("Unable to authenticate for provided credentials");
}
logger.debug("User successfully authenticated");
return responseAuthentication;
}
}
自定义authentication(一般继承UsernamePasswordAuthenticationToken,此项目是在前任的项目基础上写的)
public class ImTokenAuthentication extends TokenAuthenticationToken {
private Integer userId;
private String client;
public ImTokenAuthentication(Integer userId, String token, String client) {
super(token);
this.userId = userId;
this.client = client;
}
public ImTokenAuthentication(Integer userId, String token, String client, SecurityUserDetails details) {
super(token);
this.userId = userId;
this.client = client;
setDetails(details);
}
public Integer getUserId() {
return userId;
}
public String getClient() {
return client;
}
}
provider
public class ImAuthenticationProvider implements AuthenticationProvider {
private SecurityOrgPeopleMapper securityOrgPeopleMapper;//根据项目需求注入
private ImCheckTokenFactory imCheckTokenFactory;//根据项目需求注入
public ImAuthenticationProvider(ImCheckTokenFactory imCheckTokenFactory, SecurityOrgPeopleMapper securityOrgPeopleMapper) {
this.securityOrgPeopleMapper = securityOrgPeopleMapper;
this.imCheckTokenFactory = imCheckTokenFactory;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//自定义的装载用户信息的类
ImTokenAuthentication imTokenAuthentication = (ImTokenAuthentication) authentication;
//获取在过滤器中放入authentication的用户信息
String token = authentication.getPrincipal().toString();
Integer userId = Integer.parseInt(imTokenAuthentication.getUserId().toString());
String client = imTokenAuthentication.getClient();
//获取验证token所在的sevice
ImCheckTokenService imCheckTokenService = imCheckTokenFactory.getService(client);
if (Objects.isNull(imCheckTokenService)) {
authentication.setAuthenticated(false);
throw new UserAuthenticationException(SecurityHttpServletResponse.TOKEN_INVALID, "authenticate.fail");
}
//验证token逻辑
Object object = imCheckTokenService.checkToken(userId, token);
if (Objects.isNull(object)) {
throw new BadCredentialsException("");
}
OrgPeople orgPeople = securityOrgPeopleMapper.getPeopleBySystemUserId(userId);
imTokenAuthentication.setDetails(new SecurityUserDetails((Account) ;
//在servcice中验证不通过就已经抛出异常了,此处正常运行则设置验证通过
authentication.setAuthenticated(true);
return authentication;
}
@Override
public boolean supports(Class> authentication) {
return (ImTokenAuthentication.class.isAssignableFrom(authentication));
}
}
spring security架构
https://www.cnblogs.com/shiyu404/p/6530894.html
https://blog.csdn.net/ro_wsy/article/details/44341547
官方文档