Spring Security Oauth2 自定义DingTalkAuthenticationFilter登录钉钉小程序(以及登录 配置之后不生效 的问题)

问题

公司自研项目 做一个钉钉的小程序 。项目安全框架是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 ,问题解决

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值