Spring Security Oauth2 自定義DingTalkAuthenticationFilter登錄釘釘小程序(以及登錄 配置之后不生效 的問(wèn)題)
- 問(wèn)題
- 解決過(guò)程
- 追溯代碼
- WebSecurityConfigurer extends WebSecurityConfigurerAdapter 是配置security的初始化
- DingTalkSecurityConfigurer
- DingTalkAuthenticationProvider
- DingTalkAuthenticationToken
- 問(wèn)題 filter 配置的路徑SecurityConstants.DINGTALK_CON_TOKEN_URL 無(wú)法攔截到直接401
- 追溯代碼 FilterChainProxy
- SecurityFilterChain 中有三條FilterChain 三個(gè)過(guò)濾連中的二條的過(guò)濾條件 百分百匹配所有路徑url 所有 匹配在第三條過(guò)濾鏈中的過(guò)濾條件 /dingtalk/token 無(wú)法生效
- 解決方法
- 給 WebSecurityConfigurer 配置加上注解 @Order(1) 使/dingtalk/token的FilterChain 優(yōu)先級(jí)提高路徑就會(huì)優(yōu)先匹配,就能順利進(jìn)入DingTalkAuthenticationFilter ,問(wèn)題解決
公司自研項(xiàng)目 做一個(gè)釘釘?shù)男〕绦?。愛(ài)掏網(wǎng) - it200.com項(xiàng)目安全框架是Spring Security Oauth2 ,釘釘小程序通過(guò)授權(quán)碼在登錄本地項(xiàng)目時(shí),遇到的問(wèn)題,自定義的DingTalkAuthenticationFilter 配置的攔截路徑"/dingtalk/token" 無(wú)法攔截
追溯代碼
/auth/token 的賬號(hào)密碼登錄模式是能夠正常拿到token的
/dingtalk/token 直接報(bào)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 2024/2/1* 認(rèn)證相關(guān)配置*/
@Primary
@Order(1)
@Configuration
@Slf4j
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate ClientDetailsService clientDetailsService;@Lazy@Autowiredprivate AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;@Autowiredprivate DtalkUserService tUserService;@Autowiredprivate DingUtil dingUtil;@Autowiredprivate AuthenticationSuccessHandler mobileLoginSuccessHandler;@Autowiredprivate AuthenticationFailureHandler mobileLoginFailureHandler;@Override@SneakyThrowsprotected 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@SneakyThrowspublic AuthenticationManager authenticationManagerBean() {return super.authenticationManagerBean();}@Beanpublic AuthenticationSuccessHandler mobileLoginSuccessHandler() {return MobileLoginSuccessHandler.builder().objectMapper(objectMapper).clientDetailsService(clientDetailsService).passwordEncoder(passwordEncoder()).defaultAuthorizationServerTokenServices(defaultAuthorizationServerTokenServices).build();}@Beanpublic AuthenticationFailureHandler mobileLoginFailureHandler() {return MobileLoginFailureHandler.builder().build();}@BeanDingTalkSecurityConfigurer dingTalkSecurityConfigurer() {DingTalkSecurityConfigurer configurer = new DingTalkSecurityConfigurer(dingUtil, tUserService, mobileLoginSuccessHandler, mobileLoginFailureHandler);return configurer;}/*** https://spring.io/blog/2024/11/01/spring-security-5-0-0-rc1-released#password-storage-updated* Encoded password does not look like BCrypt** @return PasswordEncoder*/@Beanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}}
WebSecurityConfigurer extends WebSecurityConfigurerAdapter 是配置security的初始化
1.通過(guò)在 configure(HttpSecurity http){} 方法中
http.apply(dingTalkSecurityConfigurer());
2.將dingTalk相應(yīng)的配置dingTalkSecurityConfigurer初始化到 HttpSecurity中
@BeanDingTalkSecurityConfigurer 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
}
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;@Overridepublic 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;}@Overridepublic 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);}@Overridepublic Object getCredentials() {return null;}@Overridepublic Object getPrincipal() {return this.principal;}@Overridepublic 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);}@Overridepublic void eraseCredentials() {super.eraseCredentials();}}
問(wèn)題 filter 配置的路徑SecurityConstants.DINGTALK_CON_TOKEN_URL 無(wú)法攔截到直接401
追溯代碼 FilterChainProxy
private List