Security过滤器的源码解析:https://blog.csdn.net/u013825231/article/details/81144569
token校验源码解析:https://blog.csdn.net/bluuusea/article/details/80284458
前言
目前手上几个项目用的权限认证都是Spring security,之前通过逛博看底层源码也多少研究了一下,今天就专门来整理一下这一块内容。整个配置下面都有代码块展示,直接复制即可使用。
认证流程
1、通过过滤器过滤到用户请求的接口,获取身份信息(假如有多个认证方式会配置provider的顺序)
2、一般将身份信息封装到封装成Authentication下的实现类UsernamePasswordAuthenticationToken中
3、通过AuthenticationManager 身份管理器(通过配置找到对应的provider)负责验证这个UsernamePasswordAuthenticationToken
4、认证成功后(认证逻辑一般在service中),AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
5、SecurityContextHolder安全上下文容器将第2步填充了信息的UsernamePasswordAuthenticationToken,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中来建立安全上下文(security context)。
核心组件
SecurityContextHolder
SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认是使用ThreadLocal实现的,这样就保证了本线程内所有的方法都可以获得SecurityContext对象。
可以通此方法过来获取当前操作用户信息:
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
默认返回的对象是UserDetails实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。比如下面只对token进行操作就可以吧token作为属性放入UserDetails实现类中。
Authentication
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判断类型,强转成对应的具体实现类。接口详细解读如下:
getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。
AuthenticationManager
AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中身份认证的方式有多种,一般不使用AuthenticationManager,而是使用AuthenticationManager的实现类ProviderManager ,ProviderManager内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式对应不同的AuthenticationProvider。
总结:
SecurityContextHolder:存放身份信息的容器
Authentication:用户信息的抽象
AuthenticationManager:身份认证器
结合springboot实现对token验证
1、场景
拦截api/的所有接口进行验证,验证token用户与id用户是否一致,不一致或token过期则没有权限访问
2、实现
1、添加security相关依赖:spring-boot-starter-security spring-security-oauth2
<!--权限spring security-->
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<!--权限spring security-->
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<!--oauth2-->
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${spring-security-oauth.version}</version>
</dependency>
2、全局配置类,根据不同需求配置不同的过滤器和provider(代码片段)
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private BaseUserDetailServiceImpl baseUserDetailService;
@Autowired
private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myAuthenticationProvider());
}
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/favor.ioc", "/login", "/oauth/token");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAt(getMyLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").permitAll().and()
.csrf().disable();
}
/**
* 自定义登陆过滤器
*
* @return
*/
@Bean
public MyLoginAuthenticationFilter getMyLoginAuthenticationFilter() {
MyLoginAuthenticationFilter filter = new MyLoginAuthenticationFilter();
try {
filter.setAuthenticationManager(this.authenticationManagerBean());
} catch (Exception e) {
e.printStackTrace();
}
filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
return filter;
}
/**
* 自定义密码验证
*
* @return
*/
@Bean
public MyAuthenticationProvider myAuthenticationProvider() {
MyAuthenticationProvider provider = new MyAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(baseUserDetailService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
// 使用BCrypt进行密码的hash
// provider.setPasswordEncoder(passwordEncoder());
return provider;
}
}
过滤器:
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
public class MyLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private AdminMapper adminMapper;
/**
* 登陆类型
*/
private static final String SPRING_SECURITY_RESTFUL_TYPE_KEY = "type";
public static final String SPRING_SECURITY_RESTFUL_TYPE_WECHAT = "weChat";
public static final String SPRING_SECURITY_RESTFUL_TYPE_PHONE = "phone";
public static final String SPRING_SECURITY_RESTFUL_TYPE_DEFAULT = "user";
/**
* 登陆终端:1:移动端0:PC后台登陆
*/
private static final String SPRING_SECURITY_RESTFUL_ENCRYPTEDDATA_KEY = "encryptedData";
private static final String SPRING_SECURITY_RESTFUL_IV_KEY = "iv";
private static final String SPRING_SECURITY_RESTFUL_CODE_KEY = "js_code";
/**
* 手机 验证码
*/
private static final String SPRING_SECURITY_RESTFUL_MOBILE_KEY = "mobile";
private static final String SPRING_SECURITY_RESTFUL_IDENTIFYCODE_KEY = "identifyCode";
private static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY = "username";
private static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY = "password";
// private static final String SPRING_SECURITY_RESTFUL_WeChat_KEY = "openId";
private static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/login";
private boolean postOnly = true;
public MyLoginAuthenticationFilter() {
super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, "GET"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (postOnly && !"GET".equals(request.getMethod())) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String type = obtainParameter(request, SPRING_SECURITY_RESTFUL_TYPE_KEY);
String mobile = obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);
MyAuthenticationToken authRequest;
String principal;
String credentials;
//微信公众号登录
if (SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(type)) {
String encryptedData = obtainParameter(request, SPRING_SECURITY_RESTFUL_ENCRYPTEDDATA_KEY).replace(" ", "+");
String iv = obtainParameter(request, SPRING_SECURITY_RESTFUL_IV_KEY);
String code = obtainParameter(request, SPRING_SECURITY_RESTFUL_CODE_KEY);
//就是破解后的openid
principal = WxTools.doLogin(encryptedData, iv, code);
// int i = adminMapper.updateOpenid(encryptedData,principal);
// System.out.println(principal);
credentials = null;
}
// 手机验证码登录
else if (SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(type)) {
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);
credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_IDENTIFYCODE_KEY);
}
// 账号密码登陆
else {
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
credentials = DigestUtil.md5Hex(principal + obtainParameter(request, SPRING_SECURITY_RESTFUL_PASSWORD_KEY));
if (type == null) {
type = SPRING_SECURITY_RESTFUL_TYPE_DEFAULT;
}
}
if (principal == null) {
principal = "";
}
if (credentials == null) {
credentials = "";
}
principal = principal.trim();
// System.out.println(principal);
// System.out.println(credentials);
// String userName = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
// adminMapper.updateOpenid(obtainParameter(request, SPRING_SECURITY_RESTFUL_ENCRYPTEDDATA_KEY).replace(" ", "+"),principal);
authRequest = new MyAuthenticationToken(
principal, credentials, type, mobile);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
private String obtainParameter(HttpServletRequest request, String parameter) {
return request.getParameter(parameter);
}
}
自定义authentication
/**
* Description: 自定义 AbstractAuthenticationToken,
* 新增属性 type: 登陆类型、mobile:移动端设备id
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
public class MyAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 110L;
private final Object principal;
private Object credentials;
private String type;
private String mobile;
public MyAuthenticationToken(Object principal, Object credentials, String type, String mobile) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.type = type;
this.mobile = mobile;
this.setAuthenticated(false);
}
public MyAuthenticationToken(Object principal, Object credentials, String type, String mobile, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.type = type;
this.mobile = mobile;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public String getType() {
return this.type;
}
public String getMobile() {
return this.mobile;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
自定义的MyAbstractUserDetailsAuthenticationProvider 抽象类:
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
public abstract class MyAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new MyAbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new MyAbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
public MyAbstractUserDetailsAuthenticationProvider() {
}
/**
* 自定义验证
*
* @param var1
* @param var2
* @throws AuthenticationException
*/
protected abstract void additionalAuthenticationChecks(UserDetails var1, MyAuthenticationToken var2) throws AuthenticationException;
@Override
public final void afterPropertiesSet() throws Exception {
Assert.notNull(this.userCache, "A user cache must be set");
Assert.notNull(this.messages, "A message source must be set");
this.doAfterPropertiesSet();
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 此处修改断言自定义的 MyAuthenticationToken
Assert.isInstanceOf(MyAuthenticationToken.class, authentication, this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.onlySupports", "Only MyAuthenticationToken is supported"));
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = this.retrieveUser(username, (MyAuthenticationToken) authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User \'" + username + "\' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (MyAuthenticationToken) authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (MyAuthenticationToken) authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (MyAuthenticationToken) authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
MyAuthenticationToken result = new MyAuthenticationToken(principal, authentication.getCredentials(), ((MyAuthenticationToken) authentication).getType(), ((MyAuthenticationToken) authentication).getMobile(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
protected void doAfterPropertiesSet() throws Exception {
}
public UserCache getUserCache() {
return this.userCache;
}
public boolean isForcePrincipalAsString() {
return this.forcePrincipalAsString;
}
public boolean isHideUserNotFoundExceptions() {
return this.hideUserNotFoundExceptions;
}
/**
* 检索用户
*
* @param var1
* @param var2
* @return
* @throws AuthenticationException
*/
protected abstract UserDetails retrieveUser(String var1, MyAuthenticationToken var2) throws AuthenticationException;
public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
this.forcePrincipalAsString = forcePrincipalAsString;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
@Override
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}
@Override
public boolean supports(Class<?> authentication) {
return MyAuthenticationToken.class.isAssignableFrom(authentication);
}
protected UserDetailsChecker getPreAuthenticationChecks() {
return this.preAuthenticationChecks;
}
public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
this.preAuthenticationChecks = preAuthenticationChecks;
}
protected UserDetailsChecker getPostAuthenticationChecks() {
return this.postAuthenticationChecks;
}
public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
this.postAuthenticationChecks = postAuthenticationChecks;
}
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
}
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
private DefaultPostAuthenticationChecks() {
}
@Override
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
}
}
private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
private DefaultPreAuthenticationChecks() {
}
@Override
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked");
throw new LockedException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
} else if (!user.isEnabled()) {
MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled");
throw new DisabledException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
} else if (!user.isAccountNonExpired()) {
MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired");
throw new AccountExpiredException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
}
}
provide:
/**
* Description: 自定义密码验证
* 实现自定义的MyAbstractUserDetailsAuthenticationProvider 抽象类
* 根据登陆的类型 执行不同的校验
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
@Component
public class MyAuthenticationProvider extends MyAbstractUserDetailsAuthenticationProvider {
private PasswordEncoder passwordEncoder;
private String userNotFoundEncodedPassword;
private SaltSource saltSource;
private UserDetailsService userDetailsService;
@Resource
private SmsService smsService;
private static MyAuthenticationProvider myAuthenticationProvider;
@PostConstruct
public void init() {
myAuthenticationProvider = this;
myAuthenticationProvider.smsService = this.smsService;
}
public MyAuthenticationProvider() {
this.setPasswordEncoder((PasswordEncoder) (new PlaintextPasswordEncoder()));
}
/**
* 自定义验证
*
* @param userDetails
* @param authentication
* @throws AuthenticationException
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, MyAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedUsername = authentication.getPrincipal().toString();
String presentedPassword = authentication.getCredentials().toString();
// 验证开始
if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(authentication.getType())) {
// 微信只需要根据 SPRING_SECURITY_RESTFUL_TYPE_WECHAT 查询到微信号即可,所以此处无需验证
} else if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(authentication.getType())) {
// 手机短信登录
// 验证码验证,调用公共服务查询 key 为authentication.getPrincipal()的value, 并判断其与验证码是否匹配
int res = smsService.checkIsCorrectCode(presentedUsername, presentedPassword).getStatus();
if (res != 1) {
this.logger.debug("Authentication failed: verifyCode does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad verifyCode"));
}
} else {
// 用户名密码验证
if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
}
@Override
protected void doAfterPropertiesSet() throws Exception {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
@Override
protected final UserDetails retrieveUser(String username, MyAuthenticationToken authentication) throws AuthenticationException {
UserDetails loadedUser;
try {
// 调用loadUserByUsername时加入type前缀
loadedUser = this.getUserDetailsService().loadUserByUsername(authentication.getType() + "&:@" + username);
} catch (UsernameNotFoundException var6) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, null);
}
throw var6;
} catch (Exception var7) {
throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
}
public void setPasswordEncoder(Object passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
if (passwordEncoder instanceof PasswordEncoder) {
this.setPasswordEncoder((PasswordEncoder) passwordEncoder);
} else if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
final org.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder) passwordEncoder;
this.setPasswordEncoder(new PasswordEncoder() {
@Override
public String encodePassword(String rawPass, Object salt) {
this.checkSalt(salt);
return delegate.encode(rawPass);
}
@Override
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
this.checkSalt(salt);
return delegate.matches(rawPass, encPass);
}
private void checkSalt(Object salt) {
Assert.isNull(salt, "Salt value must be null when used with crypto module PasswordEncoder");
}
});
} else {
throw new IllegalArgumentException("passwordEncoder must be a PasswordEncoder instance");
}
}
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.userNotFoundEncodedPassword = passwordEncoder.encodePassword("userNotFoundPassword", (Object) null);
this.passwordEncoder = passwordEncoder;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setSaltSource(SaltSource saltSource) {
this.saltSource = saltSource;
}
protected SaltSource getSaltSource() {
return this.saltSource;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
}
这个方法其实最主要的也就这一句:
loadedUser = this.getUserDetailsService().loadUserByUsername(username);,我们继续往里面看,会发现loadUserByUsername其实是UserDetailsService接口下的一个方法,到这里我们就明白了,如果之后我们要自己查询数据,那么我们就必须要实现这个接口,然后重新写这个loadUserByUsername方法并返回UserDetails。
所以这里我们重写一下loadUserByUsername方法,实现UserDetailsService类
BaseUserDetailServiceImpl:
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
@Service
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class BaseUserDetailServiceImpl implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private AdminService adminService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException {
Admin admin;
String[] parameter;
int index = var1.indexOf("&:@");
if (index != -1) {
parameter = var1.split("&:@");
} else {
// 如果是 refresh_token 不分割
parameter = new String[]{MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_DEFAULT, var1};
}
if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(parameter[0])) {
// weixin登陆根据openId查询用户
Admin adminData = adminService.findAdminByOpenid(parameter[1]);
if (adminData == null) {
logger.error("找不到该用户,微信号:" + parameter[1]);
System.out.println("找不到该用户,微信号:" + parameter[1]);
throw new UsernameNotFoundException(parameter[1]);
}
admin = adminData;
} else if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(parameter[0])) {
// 验证码登录
Admin adminData = adminService.findAdminByPhone(parameter[1]);
if (adminData == null) {
logger.error("找不到该用户,手机号码:" + parameter[1]);
throw new UsernameNotFoundException("找不到该用户,手机号码:" + parameter[1]);
}
admin = adminData;
} else {
// 账号密码登陆根据用户名查询用户
Admin adminData = adminService.findAdminByUserName(parameter[1]);
if (adminData == null) {
logger.error("找不到该用户,用户名:" + parameter[1]);
throw new UsernameNotFoundException("找不到该用户,用户名:" + parameter[1]);
}
admin = adminData;
}
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
List<Role> roleResult = roleService.findRoleByAdminId(admin.getId());
if (roleResult != null) {
for (Role role : roleResult) {
//角色必须是ROLE_开头,可以在数据库中设置
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getValue());
grantedAuthorities.add(grantedAuthority);
//获取权限
List<Permission> perResult = permissionService.getPermissionByRoleId(role.getId());
if (perResult != null) {
for (Permission permission : perResult) {
GrantedAuthority authority = new SimpleGrantedAuthority(permission.getUrl());
grantedAuthorities.add(authority);
}
}
}
}
// 返回带有用户权限信息的User
User user = new User(admin.getUsername(), admin.getPassword(), isActive(admin.getStatus()), true, true, true, grantedAuthorities);
return user;
}
//判断账号是否禁用
private boolean isActive(int status) {
return status == 1 ? true : false;
}
}
生成的Token选择存入redis或者存入数据库,方便后面访问接口进行校验
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
/**
* 后加的
*/
@Autowired
private BaseUserDetailServiceImpl userDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* token存储redis 二选一
*/
@Bean
RedisTokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
/**
* token存储数据库 二选一
*
* @return
*/
// @Bean
// public JdbcTokenStore jdbcTokenStore() {
// return new JdbcTokenStore(dataSource);
// }
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Bean
public WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator() {
return new MssWebResponseExceptionTranslator();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(redisTokenStore())
// 二选一
// .tokenStore(jdbcTokenStore())
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager);
endpoints.tokenServices(defaultTokenServices());
//认证异常翻译
endpoints.exceptionTranslator(webResponseExceptionTranslator());
}
@Primary
@Bean
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(redisTokenStore());
// 二选一
// tokenServices.setTokenStore(jdbcTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientDetails());
//access_token 有效期自定义设置,默认12小时
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
//refresh_token 默认30天,这里修改
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
return tokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()");
security.checkTokenAccess("isAuthenticated()");
security.allowFormAuthenticationForClients();
}
}
最后就是创建两个登录成功或者登陆失败的响应类了CustomAuthenticationSuccessHandler和CustomAuthenticationFailureHandler
CustomAuthenticationSuccessHandler:
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class.getName());
@Autowired
private AdminService adminService;
@Autowired
private AdminMapper adminMapper;
@Autowired
private AreaService areaService;
@Autowired
private LogService logService;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private ConsumerTokenServices consumerTokenServices;
public CustomAuthenticationSuccessHandler() {
logger.info("CustomAuthenticationSuccessHandler loading ...");
}
/**
* 登录成功被调用
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// authentication:封装认证信息(用户信息等)
String loginIp = getIpAddress(request);
Admin currentUser = adminService.findAdminByUserName(authentication.getName());
String header = request.getHeader("Authorization");
String loginType = request.getParameter("type");
String openid = request.getParameter("openid");
String username = request.getParameter("username");
if (!StringUtils.isEmpty(openid)) {
adminMapper.updateOpenid(username, openid);
}
//没有client信息
if (header == null || !header.startsWith("Basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中无client信息");
}
//base64解码获取clientId、clientSecret
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在,clientId:" + clientId);
} else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
throw new UnapprovedClientAuthenticationException("clientSecret不匹配,clientId:" + clientId);
}
//创建accessToken
JSONObject jsonObject = generateToken(authentication, clientId, clientDetails);
// //判断异地登陆
// if (!"0:0:0:0:0:0:0:1".equals(loginIp)) {
// boolean offsiteLanding = isOffsiteLanding(currentUser.getUsername(), loginIp);
// //是异地登陆
// if (offsiteLanding) {
// consumerTokenServices.revokeToken(jsonObject.get("access_token").toString());
// jsonObject = generateToken(authentication, clientId, clientDetails);
// }
// }
//创建accessToken
// TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
//
// OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
//
// OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
//
// OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
response.setContentType("application/json;charset=UTF-8");
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT+0800' (中国标准时间)", Locale.ENGLISH);
jsonObject.put("adminId", currentUser.getId());
jsonObject.put("username", currentUser.getUsername());
jsonObject.put("realname", currentUser.getRealname());
jsonObject.put("post", currentUser.getPost());
jsonObject.put("currentTime", sdf.format(DateTools.currentTime()));
//微信菜单
// List<Map> menuByUser = adminService.getMenuByUser(currentUser.getId(), 0);
// for (Map map : menuByUser) {
// map.put("img", WxMenuImgEnum.getWxMenuImgEnum(map.get("id").toString()));
// }
// jsonObject.put("wxmenu", menuByUser);
//PC端
if ("user".equals(loginType) || "phone".equals(loginType)) {
jsonObject.put("menu", adminService.getMenuByUser(currentUser.getId(), 1));
jsonObject.put("type", adminService.getRoleByUser(currentUser.getId()));
jsonObject.put("loginCount", logService.addLoginLog(currentUser.getId(), currentUser.getUsername(), loginIp));
//小程序
} else {
jsonObject.put("type", adminService.getRoleByUser(currentUser.getId()));
}
PLATFORM_LOGGER.info("用户登录平台:" + currentUser.getRealname());
//跨域
response.setHeader("Access-Control-Allow-Origin", "*");
response.getWriter().write(objectMapper.writeValueAsString(new BaseResp(ResultStatus.SUCCESS, jsonObject)));
}
/**
* base64解码请求头 Basic aW1vb2M6aW1vb2NzZWNyZXQ=
* Decodes the header into a username and password.
*
* @throws BadCredentialsException if the Basic header is not present or is not valid
* Base64
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
throws IOException {
//Basic aW1vb2M6aW1vb2NzZWNyZXQ= 截取Basic后的
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
//解码后格式 用户名:密码
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
//返回的数组是 [用户名(就是client_id),clientSecret] 其实就是配置的
/**
* security.oauth2.client.clientId = imooc
security.oauth2.client.clientSecret = imoocsecret
*/
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
/**
* 是否异地登陆
* 传入是手机就不进来 所以传进来的IP一定是电脑登陆的IP
*
* @param username
* @param ip
* @return true是异地登陆 flase同一地点登陆
*/
private boolean isOffsiteLanding(String username, String ip) {
List<Log> lastLogins = logService.getLastLogin(username);
if (lastLogins == null || lastLogins.size() == 0) {
return false;
}
//如果一样 代表手机登陆 不挤掉,在往上一次查找
if ("0:0:0:0:0:0:0:1".equals(lastLogins.get(0).getIpAddress())) {
if (lastLogins.size() == 1) {
return false;
}
//同一地点登陆
if (ip.equals(lastLogins.get(1).getIpAddress())) {
return false;
}
return true;
}
if (ip.equals(lastLogins.get(0).getIpAddress())) {
return false;
}
return true;
}
/**
* 生成token
*
* @param authentication
* @return
* @throws JsonProcessingException
*/
private JSONObject generateToken(Authentication authentication, String clientId, ClientDetails clientDetails) throws JsonProcessingException {
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
String accessTokenStr = objectMapper.writeValueAsString(accessToken);
JSONObject jsonObject = JSONObject.fromObject(accessTokenStr);
return jsonObject;
}
CustomAuthenticationFailureHandler:
/**
* Description: No Description
*
* @author ZhuZiKai
* @date 2020/2/14 0014
*/
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class.getName());
@Autowired
private ObjectMapper objectMapper;
public CustomAuthenticationFailureHandler() {
logger.info("CustomAuthenticationFailureHandler loading ...");
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
BaseResp result = new BaseResp();
if (exception instanceof BadCredentialsException) {
result.setStatus(ResultStatus.pwd_not_exist.getErrorStatus());
result.setMessage(ResultStatus.pwd_not_exist.getErrorMsg());
} else if (exception instanceof UsernameNotFoundException) {
//用户不存在
result.setStatus(ResultStatus.user_not_exist.getErrorStatus());
result.setData(exception.getMessage());
result.setMessage(ResultStatus.user_not_exist.getErrorMsg());
} else {
result.setStatus(ResultStatus.FAIL.getErrorStatus());
result.setMessage("登陆失败!");
}
//exception:认证过程中的错误
logger.error("Authentication does not pass" + ":密码错误");
int status = HttpStatus.OK.value();
response.setStatus(status);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}
总结
代码复制即用,有什么问题欢迎留言,哪里写的不对的话也可以直接怼我
这是一位对spring security有特别研究得一位大佬:
https://felord.cn/categories/spring-security/