Spring Security Oauth2 自定义DingTalkAuthenticationFilter登录钉钉小程序(以及登录 配置之后不生效 的问题)
- 问题
- 解决过程
- 追溯代码
- WebSecurityConfigurer extends WebSecurityConfigurerAdapter 是配置security的初始化
- DingTalkSecurityConfigurer
- DingTalkAuthenticationProvider
- DingTalkAuthenticationToken
- 问题 filter 配置的路径SecurityConstants.DINGTALK_CON_TOKEN_URL 无法拦截到直接401
- 追溯代码 FilterChainProxy
- SecurityFilterChain 中有三条FilterChain 三个过滤连中的二条的过滤条件 百分百匹配所有路径url 所有 匹配在第三条过滤链中的过滤条件 /dingtalk/token 无法生效
- 解决方法
问题
公司自研项目 做一个钉钉的小程序 。项目安全框架是Spring Security Oauth2 ,钉钉小程序通过授权码在登录本地项目时,遇到的问题,自定义的DingTalkAuthenticationFilter 配置的拦截路径"/dingtalk/token" 无法拦截
解决过程
追溯代码
/auth/token 的账号密码登录模式是能够正常拿到token的
/dingtalk/token 直接报401
package com.sinoecare.tms.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sinoecare.tms.config.dingtalk.DingTalkSecurityConfigurer;
import com.sinoecare.tms.constant.SecurityConstants;
import com.sinoecare.tms.handler.MobileLoginFailureHandler;
import com.sinoecare.tms.handler.MobileLoginSuccessHandler;
import com.sinoecare.tms.service.DtalkUserService;
import com.sinoecare.tms.util.DingUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* @author liushiwei
* @date 2019/2/1
* 认证相关配置
*/
@Primary
@Order(1)
@Configuration
@Slf4j
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ClientDetailsService clientDetailsService;
@Lazy
@Autowired
private AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;
@Autowired
private DtalkUserService tUserService;
@Autowired
private DingUtil dingUtil;
@Autowired
private AuthenticationSuccessHandler mobileLoginSuccessHandler;
@Autowired
private AuthenticationFailureHandler mobileLoginFailureHandler;
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
log.info("初始化auth");
http
.antMatcher(SecurityConstants.DINGTALK_CON_TOKEN_URL)
.authorizeRequests()
.antMatchers(
"/actuator/**",
"/token/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
http.apply(dingTalkSecurityConfigurer());
}
@Bean
@Override
@SneakyThrows
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
@Bean
public AuthenticationSuccessHandler mobileLoginSuccessHandler() {
return MobileLoginSuccessHandler.builder()
.objectMapper(objectMapper)
.clientDetailsService(clientDetailsService)
.passwordEncoder(passwordEncoder())
.defaultAuthorizationServerTokenServices(defaultAuthorizationServerTokenServices).build();
}
@Bean
public AuthenticationFailureHandler mobileLoginFailureHandler() {
return MobileLoginFailureHandler.builder().build();
}
@Bean
DingTalkSecurityConfigurer dingTalkSecurityConfigurer() {
DingTalkSecurityConfigurer configurer = new DingTalkSecurityConfigurer(dingUtil, tUserService, mobileLoginSuccessHandler, mobileLoginFailureHandler);
return configurer;
}
/**
* https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
* Encoded password does not look like BCrypt
*
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
WebSecurityConfigurer extends WebSecurityConfigurerAdapter 是配置security的初始化
1.通过在 configure(HttpSecurity http){} 方法中
http.apply(dingTalkSecurityConfigurer());
2.将dingTalk相应的配置dingTalkSecurityConfigurer初始化到 HttpSecurity中
@Bean
DingTalkSecurityConfigurer dingTalkSecurityConfigurer() {
DingTalkSecurityConfigurer configurer = new DingTalkSecurityConfigurer(dingUtil, tUserService, mobileLoginSuccessHandler, mobileLoginFailureHandler);
return configurer;
}
3.dingTalkAuthenticationProvider带入 DtalkUserService dtalkUserService
4.dingTalkSecurityConfigurer的configure(HttpSecurity http)方法中DingTalkAuthenticationFilter配置到filterChain中并加入dingTalkAuthenticationProvider
http.authenticationProvider(dingTalkAuthenticationProvider)
.addFilterAfter(dingTalkAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
DingTalkSecurityConfigurer
package com.sinoecare.tms.config.dingtalk;
import com.sinoecare.tms.filter.DingTalkAuthenticationFilter;
import com.sinoecare.tms.service.DtalkUserService;
import com.sinoecare.tms.util.DingUtil;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@AllArgsConstructor
public class DingTalkSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final DingUtil dingUtil;
private final DtalkUserService dtalkUserService;
private final AuthenticationSuccessHandler mobileLoginSuccessHandler;
private final AuthenticationFailureHandler mobileLoginFailureHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
DingTalkAuthenticationFilter dingTalkAuthenticationFilter = new DingTalkAuthenticationFilter(dingUtil);
dingTalkAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
dingTalkAuthenticationFilter.setAuthenticationSuccessHandler(mobileLoginSuccessHandler);
dingTalkAuthenticationFilter.setAuthenticationFailureHandler(mobileLoginFailureHandler);
DingTalkAuthenticationProvider dingTalkAuthenticationProvider = new DingTalkAuthenticationProvider();
dingTalkAuthenticationProvider.setDtalkUserService(dtalkUserService);
http.authenticationProvider(dingTalkAuthenticationProvider)
.addFilterAfter(dingTalkAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
DingTalkAuthenticationProvider
package com.sinoecare.tms.config.dingtalk;
import java.time.LocalDateTime;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import com.sinoecare.tms.bean.DtalkUser;
import com.sinoecare.tms.bean.VillcloudUser;
import com.sinoecare.tms.constant.SecurityConstants;
import com.sinoecare.tms.service.DtalkUserService;
import cn.hutool.core.collection.CollUtil;
public class DingTalkAuthenticationProvider implements AuthenticationProvider {
private DtalkUserService dtalkUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
DingTalkAuthenticationToken dingTalkAuthenticationToken = (DingTalkAuthenticationToken) authentication;
DtalkUser user = dtalkUserService.getTUserByDDId((String) dingTalkAuthenticationToken.getPrincipal());
/*
(String username, String id, String areaId, String avatar, String phone, String password,
boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, String unitId,
Collection<? extends GrantedAuthority> authorities, String client, String realName, LocalDateTime lastLoginTime,
String unitName,String roleId)
* */
VillcloudUser user1 = new VillcloudUser(user.getName(), user.getId(), null, user.getAvatar(), user.getPhone(),
SecurityConstants.WECHAT_CON_PWD, true, true, true, true, null,
CollUtil.newArrayList(), null, user.getName(), LocalDateTime.now(), null, null);
if (user == null) {
throw new InternalAuthenticationServiceException("dingUserID不存在:" + dingTalkAuthenticationToken.getPrincipal());
}
DingTalkAuthenticationToken authenticationToken = new DingTalkAuthenticationToken(user1, null);
authenticationToken.setDetails(dingTalkAuthenticationToken.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return DingTalkAuthenticationToken.class.isAssignableFrom(authentication);
}
public DtalkUserService getDtalkUserService() {
return dtalkUserService;
}
public void setDtalkUserService(DtalkUserService dtalkUserService) {
this.dtalkUserService = dtalkUserService;
}
}
DingTalkAuthenticationToken
package com.sinoecare.tms.config.dingtalk;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class DingTalkAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
public DingTalkAuthenticationToken(String openId) {
super(null);
this.principal = openId;
setAuthenticated(false);
}
public DingTalkAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@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");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
问题 filter 配置的路径SecurityConstants.DINGTALK_CON_TOKEN_URL 无法拦截到直接401
追溯代码 FilterChainProxy
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
SecurityFilterChain 中有三条FilterChain 三个过滤连中的二条的过滤条件 百分百匹配所有路径url 所有 匹配在第三条过滤链中的过滤条件 /dingtalk/token 无法生效
解决方法
给 WebSecurityConfigurer 配置加上注解 @Order(1) 使/dingtalk/token的FilterChain 优先级提高路径就会优先匹配,就能顺利进入DingTalkAuthenticationFilter ,问题解决