oauth2对用户状态进行修改与校验(二)—— 用户状态校验

oauth2对用户状态进行修改与校验(二)—— 用户状态校验

源码流程

查看源码发现在JdbcUserDetailsService的loadUserByUsername获取到用户以后,自带了一个状态校验的接口

	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
			// ...省略
			
			try {
				// 在这个方法中调用了loadUserByUsername方法拿到用户信息
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				// ...
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
			// 发现这里有一个check方法
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// ...
			}
			else {
				throw exception;
			}
		}
		// ...
	}

点进去发现可以自定义任何想要的用户状态检查

/**
 * Called by classes which make use of a {@link UserDetailsService} to check the status of
 * the loaded <tt>UserDetails</tt> object. Typically this will involve examining the
 * various flags associated with the account and raising an exception if the information
 * cannot be used (for example if the user account is locked or disabled), but a custom
 * implementation could perform any checks it wished.
 * <p>
 * The intention is that this interface should only be used for checks on the persistent
 * data associated with the user. It should not involved in making any authentication
 * decisions based on a submitted authentication request.
 *
 * @author Luke Taylor
 * @since 2.0
 *
 * @see org.springframework.security.authentication.AccountStatusUserDetailsChecker
 * @see org.springframework.security.authentication.AccountStatusException
 */
public interface UserDetailsChecker {
	/**
	 * Examines the User
	 * @param toCheck the UserDetails instance whose status should be checked.
	 */
	void check(UserDetails toCheck);
}

用户校验

因此我们继承UserDetailsChecker类,重写check方法

public class UserDetailsCheck implements UserDetailsChecker{
	@Override
    public void check(UserDetails toCheck) {
        Assert.isTrue(toCheck instanceof JdbcUserDetails, "用户类型错误!");

        JdbcUserDetails userDetails = (JdbcUserDetails) toCheck;
        String status = userDetails.getStatus();
        int errorNum = userDetails.getLoginErrorNum();
        Date date = userDetails.getLoginDate();

        switch (status) {
            case UserStatusConstant.DISABLE_CODE:
                int time = isArriveLoginTime(errorNum, date);
                if (time > 0) {
                    throw new LockedException("连续错误次数过多!请"+time+"分钟后重试");
                }
                break;
            case UserStatusConstant.FREEZE_CODE:
                throw new LockedException("账户已被冻结,请联系管理员!");
            case UserStatusConstant.STOP_CODE:
                throw new DisabledException("账户已被停用,请联系管理员!");
            default:
        }
}

对象注入

最后把UserDetailsChecker注入进来,这里原本UserDetails是在configure(HttpSecurity http) 方法里面注入的,but没有找到能够把UserDetailsChecker也在config里面放进去的方法。最后看了一下源码,自己定义了一个AuthenticationProvider,把UserDetails和UserDetailsChecker都丢进去。

但是又出现了部分异常提示是英文的问题,反正最后研究了很久,看起来是项目启动的时候,MessageSource加载顺序的问题。正常是应该加载new SpringSecurityMessageSource(),但是自定义的情况下会被另一个覆盖掉。
最后研究了很久,加上了doAfterPropertiesSet方法来指定MessageSource才好的。

    @Bean
    public UserDetailsService jdbcUserDetailsService() {
        return new JdbcUserDetailsService();
    }

    @Bean
    public UserDetailsChecker userDetailsChecker() {
        return new UserDetailsCheck();
    }

    @Bean
    public AuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService, UserDetailsChecker userDetailsCheck, BCryptPasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider impl = new DaoAuthenticationProvider(){
            @Override
            protected void doAfterPropertiesSet() {
                this.setMessageSource(new SpringSecurityMessageSource());
            }
        };
        impl.setUserDetailsService(userDetailsService);
        impl.setPreAuthenticationChecks(userDetailsCheck);
        impl.setPasswordEncoder(passwordEncoder);
        return impl;
    }

写在最后

时间稍微有点久了,其实中间遇到了很多坑。
最开始状态校验是直接放在loadUserByUsername方法中的,大概是这样:

public class JdbcUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDetailsServiceMapper userDetailsServiceMapper;

    @Autowired
    private PasswordEncoderConfiguration.CustomizePasswordEncoder customizePasswordEncoder;

    @Override
    public UserDetails loadUserByUsername(String encodeUserName) throws UsernameNotFoundException, DeclareException {
        String userName = customizePasswordEncoder.decodeSm4(encodeUserName);
        User user = this.userDetailsServiceMapper.selectUserByUserPhone(userName);

        if (user == null) {
            throw new UsernameNotFoundException(userName);
        }
		// 这里加入status的校验
		check(user.getStatus());
        }
        return new JdbcUserDetails(user);
    }
    
    public void check(String status) {
		switch (status) {
            case UserStatusConstant.DISABLE_CODE:
                int time = isArriveLoginTime(errorNum, date);
                if (time > 0) {
                    throw new LockedException("连续错误次数过多!请"+time+"分钟后重试");
                }
                break;
            case UserStatusConstant.FREEZE_CODE:
                throw new LockedException("账户已被冻结,请联系管理员!");
            case UserStatusConstant.STOP_CODE:
                throw new DisabledException("账户已被停用,请联系管理员!");
            default:
	}
    
}

问题是异常一直抛不出去,看源码的时候发现方法形式如下:

authenticate{
	try {
		retrieveUser{
			// 该方法中调用了loadUserByUsername
			// 但是这里的异常,只有UsernameNotFoundException可能被抛出,其他都会被归
			// 为InternalAuthenticationServiceException抛出去
			catch (UsernameNotFoundException ex) {
				mitigateAgainstTimingAttack(authentication);
				throw ex;
			}
			catch (InternalAuthenticationServiceException ex) {
				throw ex;
			}
			catch (Exception ex) {
				throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
			}
		}
	catch (UsernameNotFoundException ex) {
		// 外层也只会捕获UsernameNotFoundException异常,其他异常不管直接向上抛
		// 最后直接在modelAndView出去了,返回了一个页面出来。而不是返回值。
	}
	
	try {
		// 这是我们后来写的check方法
		check(user);
	catch (AuthenticationException exception){
		// ...这里的异常才是用户认证的异常处理
		// 因此用户状态校验在这里做是没问题的,不能在内部的loadUserByUsername方法中做
	}
}

现在项目的修改已经不少了,刚刚本来为了还原当时的错误,直接在loadUserByUsername中抛了一个
LockedException,竟然正常返回了错误信息也不是一整个页面,可能也有其他的影响因素吧~不去找了。

如果大家看到可以测试一下噢~

===================
。。 ◣▲▲◢。。。
。 . ◢████◣。。
。> ██████<。
。 . ◥████◤。。
。 。 ◥██◤。。。
。。。 ◥◤。。。。
。草莓〔〕棒棒糖。
。。。〔〕。。。。
。。。〔〕。。。。
。。。〔〕。。。。
===================

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security OAuth2提供了多种方式来进行自定义用户校验。引用中提到了一个关键类DefaultWebResponseExceptionTranslator,它实现了WebResponseExceptionTranslator<OAuth2Exception>接口,用于将异常类统一转换成OAuth2Exception,从而借助HttpMesssageConverters来将OAuth2Exception异常转换成错误响应的格式。这个类可以在异常处理中起到关键作用。 另外,Spring Security OAuth2还提供了AuthenticationManager来处理用户认证。在AuthorizationServerSecurityConfigurer中,可以通过HttpSecurityBuilder.getSharedObject方法获取到AuthenticationManager的实例。但在进行AuthorizationServerConfigurerAdapter的配置时,这个共享对象还未被设定,需要自定义ClientCredentialsTokenEndpointFilter并重写其中的getAuthenticationManager方法来获取AuthenticationManager的实例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [spring security oauth:自定义异常处理(授权服务)](https://blog.csdn.net/qq_43878324/article/details/123142025)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [【Spring Security Oauth2】构建授权服务器(五):自定义(用户登录)认证策略](https://blog.csdn.net/apple_csdn/article/details/123389101)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值