spring security主要分为两部分,认证(authentication)和授权(authority)。
这一篇主要是认证部分,它由 ProviderManager(AuthenticationManager)实现。具体层次结构如下:
认证的核心就是登录,这里简单介绍下security自定义token登录的实现逻辑,同时兼容用户名密码登录。
大体分为以下几个步骤:
github代码路径
注:仅说明实现方式,逻辑简化处理。
package demo.model;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
*
* @Description: 声明自定义token,是为后面的AuthenticationProvider提供支撑,区分不同类型的处理。
*
* @auther: csp
* @date: 2019/1/7 下午6:25
*
*/
public class LoginToken extends AbstractAuthenticationToken {
private final String token;
public LoginToken(String token) {
super(null);
this.token = token;
}
public LoginToken(String token, Collection extends GrantedAuthority> authorities) {
super(authorities);
this.token = token;
setAuthenticated(true);
}
// 这个地方传递下token,逻辑是简化的逻辑,具体可以根据实际场景处理。
// 如jwt token,解析出来username等信息,放到该token中。
@Override
public Object getCredentials() {
return this.token;
}
@Override
public Object getPrincipal() {
return null;
}
}
package demo.filter;
import demo.model.LoginToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @Description: 自定义filter,用来筛选出来想要的登录方式。
*
* @auther: csp
* @date: 2019/1/7 下午6:27
*
*/
public class MyTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SPRING_SECURITY_RESTFUL_TOKEN = "token";
public static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/tokenLogin";
private boolean postOnly = true;
// 请求路径声明,url不能被权限拦截。
// 会根据AntPathRequestMatcher 筛选请求,符合条件的才会认为有效
public MyTokenAuthenticationFilter() {
super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, null));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
AbstractAuthenticationToken authRequest;
String token = obtainParameter(request, SPRING_SECURITY_RESTFUL_TOKEN);
authRequest = new LoginToken(token);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// 根据AuthenticationManager校验具体的请求,实际的登录验证触发。
return this.getAuthenticationManager().authenticate(authRequest);
}
private void setDetails(HttpServletRequest request,
AbstractAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
private String obtainParameter(HttpServletRequest request, String parameter) {
String result = request.getParameter(parameter);
return result == null ? "" : result;
}
}
package demo.provider;
import demo.model.LoginToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
*
* @Description: token验证逻辑
*
* @auther: csp
* @date: 2019/1/7 下午9:05
*
*/
public class MyTokenProvider implements AuthenticationProvider {
UserDetailsService userDetailsService;
public MyTokenProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (authentication.getCredentials() == null) ? "NONE_PROVIDED"
: (String) authentication.getCredentials();
// loginToken_user
// 这个地方简化处理,实际需要校验token,如jwt token 需要解密 验证信息
if (token.startsWith("loginToken_")) {
// 验证下token对不对,然后加载下信息。
String userName = token.split("_")[1];
UserDetails user = userDetailsService.loadUserByUsername(userName);
LoginToken result = new LoginToken(token, user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
throw new BadCredentialsException("token无效");
}
/**
*
* @Description: 只处理特定类型的登录
*
* @auther: csp
* @date: 2019/1/7 下午9:03
* @param authenticationClass
* @return: boolean
*
*/
@Override
public boolean supports(Class> authenticationClass) {
return (LoginToken.class
.isAssignableFrom(authenticationClass));
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
package demo.service;
import demo.model.UrlGrantedAuthority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
*
* @Description: 用户信息查询逻辑,这里token认证和用户名登录使用同一个service
*
* @auther: csp
* @date: 2019/1/7 下午9:06
*
*/
@Component public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("用户的用户名: {}", username);
List list = new ArrayList();
// 模拟下逻辑,简单处理下。
if ("admin".equals(username)) {
// 自定义权限实现
UrlGrantedAuthority authority = new UrlGrantedAuthority(null, "/admin/index");
list.add(authority);
// 封装用户信息,并返回。参数分别是:用户名,密码,用户权限
User user = new User(username, "123456", list);
return user;
}
else if ("user".equals(username)) {
list.add(new SimpleGrantedAuthority("ROLE_USER"));
User user = new User(username, "123456", list);
return user;
}
else {
throw new DisabledException("用户不存在");
}
}
}
package demo.config;
import demo.filter.MyTokenAuthenticationFilter;
import demo.provider.MyTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService myUserDetailsService;
// @formatter:off
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 将tokenfilter追加进去,筛选出来tokenLogin逻辑。
.addFilterBefore(getTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.logout().logoutUrl("/logout").logoutSuccessUrl("/").and()
.formLogin().loginPage("/login").defaultSuccessUrl("/").failureUrl("/login-error").permitAll().and()
.authorizeRequests()
.antMatchers(MyTokenAuthenticationFilter.SPRING_SECURITY_RESTFUL_LOGIN_URL).permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated();
}
// @formatter:on
@Override
public void configure(WebSecurity web) throws Exception {
//忽略请求 不走security filters
web.ignoring().antMatchers("/login-error2","/css/**","/info","/health","/hystrix.stream");
}
/**
* 1、用户验证,指定多个AuthenticationProvider
* 实际执行时候根据provider的supports方法判断是否走逻辑
*
* 2、如果不覆盖,优先会获取AuthenticationProvider bean作为provider;
* 如果没有bean,默认提供DaoAuthenticationProvider
*
* @param auth
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myTokenProvider());
// 未配置时候用户名密码默认登录provider
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider1 = new DaoAuthenticationProvider();
// 设置userDetailsService
provider1.setUserDetailsService(myUserDetailsService);
// 禁止隐藏用户未找到异常
provider1.setHideUserNotFoundExceptions(false);
// 使用BCrypt进行密码的hash
// provider1.setPasswordEncoder(myEncoder());
return provider1;
}
/**
*
* @Description: 自定义token方式认证逻辑provider
*
* @auther: csp
* @date: 2019/1/7 下午9:18
* @return: demo.provider.MyTokenProvider
*
*/
@Bean
public MyTokenProvider myTokenProvider() {
return new MyTokenProvider(myUserDetailsService);
}
// @Bean
public BCryptPasswordEncoder myEncoder(){
return new BCryptPasswordEncoder(6);
}
/**
* token登录过滤器,用来筛选出来token登录方式。
*/
@Bean
public MyTokenAuthenticationFilter getTokenAuthenticationFilter() {
MyTokenAuthenticationFilter filter = new MyTokenAuthenticationFilter();
try {
// 使用的是默认的authenticationManager
filter.setAuthenticationManager(this.authenticationManagerBean());
} catch (Exception e) {
e.printStackTrace();
}
// filter.setAuthenticationSuccessHandler(new MyLoginAuthSuccessHandler());
filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/"));
filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login-error2"));
return filter;
}
}
- 用户名密码登录:
http://127.0.0.1:9999/
admin 123456
user 123456
- token登录:
user登录:
http://127.0.0.1:9999/tokenLogin?token=loginToken_user
admin登录:
http://127.0.0.1:9999/tokenLogin?token=loginToken_admin