spring oauth2.0 多种用户登录

近期遇到一个问题,在spring oauth2.0默认的密码登录校验中,只能访问单个数据库表,但针对不同的表用户,需要访问不同的表,所以需要传一个新参去判断用户访问不同的表来校验账号密码

本文主要是是讨论oauth2.0支持多表用户登录

我使用的Spring Boot为2.2.5.RELEAS,SpringCloud为Hoxton.SR2

对于多个表用户,需要传递不同的参数来区分是访问哪个表,所以在请求参数中增加了loginType来区分不同的用户

在ResourceServerConfig中配置中配置自定义的配置类

spring oauth2.0 多种用户登录_第1张图片

创建 MultipleLoginAuthenticationSecurityConfig,自义定配置 extends SecurityConfigurerAdapter

复写config配置

@Component
public class MultipleLoginAuthenticationSecurityConfig  extends SecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler myAuthenticationFailureHandler;

    @Resource
    private MyUserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(HttpSecurity http) {
        MultipleLoginAuthenticationFilter multipleLoginAuthenticationFilter = new MultipleLoginAuthenticationFilter();
        multipleLoginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        multipleLoginAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        multipleLoginAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);

        MultipleLoginAuthenticationProvider multipleLoginAuthenticationProvider = new MultipleLoginAuthenticationProvider();
        multipleLoginAuthenticationProvider.setUserDetailsService(userDetailsService);
        multipleLoginAuthenticationProvider.setPasswordEncoder(passwordEncoder);

        http.authenticationProvider(multipleLoginAuthenticationProvider)
                .addFilterBefore(multipleLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    }
}
UsernamePasswordAuthenticationFilter是spring security提供的表单登录Filter
创建一个新的过滤器
public class MultipleLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private String loginParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MULTIPLE;
    private boolean postOnly = true;


    public MultipleLoginAuthenticationFilter() {
        super();
    }

    /**
     * 复写新的过滤器方法,并构造新的authenticationToken
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        // 获取登录传递的账号密码
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        MultipleLoginAuthenticationToken authRequest = new MultipleLoginAuthenticationToken(username,password);
        this.setDetails(request, authRequest);
        // 用户寻找自定义的校验Provider
        return this.getAuthenticationManager().authenticate(authRequest);
    }


    /**
     * @param request     获取请求的参数
     * @param authRequest
     */
    protected void setDetails(HttpServletRequest request, MultipleLoginAuthenticationToken authRequest) {
        Map map = new LinkedHashMap<>();
        Enumeration paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            String[] paramValues = request.getParameterValues(paramName);
            if (paramValues.length == 1) {
                String paramValue = paramValues[0];
                if (paramValue.length() != 0) {
                    map.put(paramName, paramValue);
                }
            }
        }
        authRequest.setDetails(map);
    }


    public void setLoginParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.loginParameter = usernameParameter;
    }


    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getLoginParameter() {
        return loginParameter;
    }
}

所以我们需要新创建一个新的provider来替代自带的的密码校验方式

@Slf4j
public class MultipleLoginAuthenticationProvider implements AuthenticationProvider {

    @Getter
    @Setter
    private MyUserDetailsService userDetailsService;

    @Getter
    private PasswordEncoder passwordEncoder;

    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

    @Getter
    @Setter
    private volatile String userNotFoundEncodedPassword;

    public MultipleLoginAuthenticationProvider() {
        passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    @SuppressWarnings("unchecked")
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MultipleLoginAuthenticationToken authenticationToken = (MultipleLoginAuthenticationToken) authentication;


        Map details = (Map) authenticationToken.getDetails();
        // 获取自定义的loginType参数
        String loginType = details.get("loginType");
        // 使用自定义的loginType在userDetailServceice中查询不同的数据库
        UserDetails user = this.getUserDetailsService().loadUserByLoginType(authentication.getName(), loginType);
        if (user == null) {
            throw new InternalAuthenticationServiceException(
                    "用户信息为空");
        }
        if (!"APP".equals(loginType)) {
            // app登录不校验密码
            // 获取当前输入的密码
            String presentedPassword = authentication.getCredentials().toString();
            if (!passwordEncoder.matches(presentedPassword, user.getPassword())) {
                log.error("用户名或密码错误,用户名:" + authentication.getName());
                throw new BadCredentialsException("用户名或密码错误");
            }
        }

        if (details.containsKey("password")) {
            details.put("password", null);
        }

        MultipleLoginAuthenticationToken authenticationResult = new MultipleLoginAuthenticationToken(user, user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }

    @Override
    public boolean supports(Class authentication) {
        return MultipleLoginAuthenticationToken.class.isAssignableFrom(authentication);
    }


    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
        this.userNotFoundEncodedPassword = null;
    }

}

重新一个authenticationToken,复制 UsernamePasswordAuthenticationToken

 

public class MultipleLoginAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    private Object credentials;

    public MultipleLoginAuthenticationToken(String username,String password) {
        super(null);
        this.principal = username;
        this.credentials = password;
        setAuthenticated(false);
    }

    public MultipleLoginAuthenticationToken(Object principal,
                                            Collection authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true); // must use super, as we override
    }

    public Object getCredentials() {
        return credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    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();
    }
}

以上四个文件就基本上将现有的springsecurity默认配置覆盖了

总结:

当配置了自定义配置以后,过滤器先获取了登录的传参信息,并通过下图获取自配置的provider去校验账号密码,获取认证管理器,

ProviderManager.authenticate 负责寻找认证的提供者(provider),当并在provider中校验密码

图一

	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();
// 获取所有的提供者,并比对是否为当前配置的提供者
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
                // 使用自定义的提供者用来账号密码校验(MultipleLoginAuthenticationProvider.authenticate)
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

 

你可能感兴趣的:(java,spring,cloud,oauth2)