Oauth2将密码模式改造成手机或邮箱验证码模式来授权认证

450 阅读3分钟

TokenEndpoint类中可以看到入口/oauth/token

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
	Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}

		String clientId = getClientId(principal);
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

		if (clientId != null && !clientId.equals("")) {
			// Only validate the client details if a client authenticated during this
			// request.
			if (!clientId.equals(tokenRequest.getClientId())) {
				// double check to make sure that the client ID in the token request is the same as that in the
				// authenticated client
				throw new InvalidClientException("Given client ID does not match authenticated client");
			}
		}
		if (authenticatedClient != null) {
			oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
		}
		if (!StringUtils.hasText(tokenRequest.getGrantType())) {
			throw new InvalidRequestException("Missing grant type");
		}
		if (tokenRequest.getGrantType().equals("implicit")) {
			throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
		}

		if (isAuthCodeRequest(parameters)) {
			// The scope was requested or determined during the authorization step
			if (!tokenRequest.getScope().isEmpty()) {
				logger.debug("Clearing scope of incoming token request");
				tokenRequest.setScope(Collections.<String> emptySet());
			}
		}

		if (isRefreshTokenRequest(parameters)) {
			// A refresh token has its own default scopes, so we should ignore any added by the factory here.
			tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
		}

		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
		if (token == null) {
			throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
		}

		return getResponse(token);

	}

打断点找一下源码是在哪里加入四种模式的授权的

OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

this.getTokenGranter()会调用this.tokenGranter()

在这个this.getDefaultTokenGranters()里面会添加授权模式进去

private List<TokenGranter> getDefaultTokenGranters() {
		ClientDetailsService clientDetails = clientDetailsService();
		AuthorizationServerTokenServices tokenServices = tokenServices();
		AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
		OAuth2RequestFactory requestFactory = requestFactory();

		List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
		tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
				requestFactory));
		tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
		ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
		tokenGranters.add(implicit);
		tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
		if (authenticationManager != null) {
			tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
					clientDetails, requestFactory));
                                        
                                        //这个SmsTokenGranter是仿造ResourceOwnerPasswordTokenGranter的源码写的
			tokenGranters.add(new SmsTokenGranter(authenticationManager, tokenServices,
					clientDetails, requestFactory));
                                        
                                        
		}
		return tokenGranters;
	}

SmsTokenGranter具体实现

public class SmsTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "sms";

    private final AuthenticationManager authenticationManager;

    public SmsTokenGranter(AuthenticationManager authenticationManager,
                           AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    protected SmsTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                              ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    public UserService userService = ApplicationContextAwareUtil.getBean("userServiceImpl");
    public RedisUtil redisUtil = ApplicationContextAwareUtil.getBean("redisUtil");

    /**
     * 自定义手机验证码校验
     */
    public void checkPhoneSms(Map<String, String> parameters) {
        String loginType = (String) parameters.get("loginType");
        UserModel userInfo = new UserModel();
        if (loginType.equals("email")){
            String email = (String) parameters.get("email");
            String code = (String) parameters.get("code");//验证码

            if (StringUtils.isBlank(email)) {
                throw new InvalidGrantException("邮箱不能为空!");
            }
            if (StringUtils.isBlank(code)) {
                throw new InvalidGrantException("验证码不能为空!");
            }

            String codeCache = (String) redisUtil.get(email);
            if (StringUtils.isBlank(codeCache)) {
                throw new InvalidGrantException("验证码已失效,请重新获取!");
            }
            if (!code.equals(codeCache)) {
                throw new InvalidGrantException("验证码错误!");
            }
            userInfo = userService.selectUserModelByEmail(email);
        }else if (loginType.equals("phone")){
            String phone = (String) parameters.get("phone");
            String code = (String) parameters.get("code");//验证码
            if (StringUtils.isBlank(phone)) {
                throw new InvalidGrantException("手机号不能为空!");
            }
            if (StringUtils.isBlank(code)) {
                throw new InvalidGrantException("验证码不能为空!");
            }

            String codeCache = (String) redisUtil.get(phone);
            if (StringUtils.isBlank(codeCache)) {
                throw new InvalidGrantException("验证码已失效,请重新获取!");
            }
            if (!code.equals(codeCache)) {
                throw new InvalidGrantException("验证码错误!");
            }
            userInfo = userService.selectUserModelByPhone(phone);
        }

        /**
         1、先判断是手机号模式还是邮箱模式
         2、从缓存中根据手机号或者邮箱取出验证码验证
         3、验证通过后根据手机号或者邮箱查出用户名密码,设置到parameters中,这样本质就还是走密码模式
         (如果未创建的用户在这一步可以调用创建用户的方法,同时随机生成一个密码存着)。
         */

        //设置username和password,之所以加个冒号
        //是为了在loadUserByUsername方法中和原本走密码模式的username做一个区分
        parameters.put("username", userInfo.getUserName() + ":");
        parameters.put("password", userInfo.getPassword());
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        this.checkPhoneSms(parameters);
        String username = parameters.get("username");
        String password = parameters.get("password");
        // Protect from downstream leaks of password
        parameters.remove("password");

        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (AccountStatusException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }
        catch (BadCredentialsException e) {
            // If the username/password are wrong the spec says we should send 400/invalid grant
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate user: " + username);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

}

因为在这个类里面无法直接Autowired注入我们需要的类,通过此工具类则可以直接获取spring中的bean

@Component
public class ApplicationContextAwareUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextAwareUtil.applicationContext = applicationContext;

    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        if (applicationContext == null) {
            return null;
        }
        return (T) applicationContext.getBean(name);
    }

}

最后在UserDetailsService的实现类中根据具体业务来写规则,需要注意的是之前用‘:’来区分了自定义的模式和密码模式