spring-security-oauth2 中优雅的扩展自定义(短信验证码)登录方式【Part-End】

上一篇 中我们讨论了如何实现的思路,本篇中我们把它实现

发送验证码的Controller

首先我们需要创建一个发送验证码的 Controller, 至于如何实现,这里就不多说了,大家都会的,本篇重点说明验证部分.
【注意】在认证服务器上增加自己的 Controller, 默认情况下访问是返回403,有两种办法解决:

  1. 把认证服务器也配制为资源服务器,既: 它既是认证服务器,也是资源服务器.并配制新增的Controller为任何人都能访问
  2. 关闭认证服务器的 csrf.

由于我在认证服务器上增加的都是认证相关的功能,任何人都能访问,不需要资源保护,所以我选择了第二种方法.

关闭认证服务器的 csrf

在配制认证服务器的时候,我们创建过一个org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 的子类用于安全配制. 如果你当时没有配制,增加一个就行.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();  // 关闭 csrf
    }
}

增加短信验证码的 TokenGranter

参考 org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter 的代码

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

public class SMSCodeTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "sms_code";

    public SMSCodeLoginTokenGranter(AuthorizationServerTokenServices tokenServices,
        ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
    super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client,
        TokenRequest tokenRequest) {
    
    Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
    String userMobileNo = parameters.get("username");  //客户端提交的用户名
    String smscode = parameters.get("smscode");  //客户端提交的验证码
    
    // 从库里查用户
    UserDetails user = 从库里查找用户的代码略;
    if(user == null) {
        throw new InvalidGrantException("用户不存在");
    }
    
    验证用户状态(是否警用等),代码略

    // 验证验证码
    String smsCodeCached = 获取服务中保存的用户验证码,代码略.一般我们是在生成好后放到缓存中
    if(StringUtils.isBlank(smsCodeCached)) {
        throw new InvalidGrantException("用户没有发送验证码");
    }
    if(!smscode.equals(smsCodeCached)) {
        throw new InvalidGrantException("验证码不正确");
    }else {
        验证通过后从缓存中移除验证码,代码略
    }

    
    Authentication userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    // 关于user.getAuthorities(): 我们的自定义用户实体是实现了 
    // org.springframework.security.core.userdetails.UserDetails 接口的, 所以有 user.getAuthorities()
    // 当然该参数传null也行
    ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
    
    OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);      
    return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

}

把 SMSCodeTokenGranter 加入到 CompositeTokenGranter 需要的 List<TokenGranter> 中

在上一篇中我们修改了 OAuth2AuthorizationServerConfig类,现在继续修改:
我们在 getDefaultTokenGranters 方法中加入:

tokenGranters.add(new SMSCodeLoginTokenGranter(tokenServices, clientDetails, requestFactory, userDetailsService));

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));
    }
    
    tokenGranters.add(new SMSCodeLoginTokenGranter(tokenServices, clientDetails, requestFactory, userDetailsService));
    return tokenGranters;
    }

其他代码不用修改

如何使用我们新增的短信验证码方式?

我们调用 /oauth/token 进行认证的时候,有一个 grant_type 参数,我们把它的值改为 sms_code
password 参数可以不要了,新增一个 smscode 参数
当然,上面的 "sms_code" 和 "smscode" 也是可以修改的:
"sms_code" 对应 SMSCodeTokenGranter 中的静态常量 GRANT_TYPE
"smscode" 对应 SMSCodeTokenGranter.getOAuth2Authentication 方法中的 parameters.get("smscode");

全文完...

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 于是决定来写点什么。
    Chikosaya阅读 141评论 2 0
  • 忙碌一天回来,总是不自觉地什么都不想要做,也不知道该处于什么状态,这种感觉实在是令人恐慌。于是很想哭,这种想哭不是...
    思索_BeautyLin阅读 384评论 0 1
  • 艺术品收藏 名师字画当今的投资大方向,大潮流,和艺术的精华。为什么要购买名师字画呢?购买名师字画有什么好处么? 中...
    后来呢_1911阅读 728评论 0 0
  • 当我越来做越想知道生为何物 当我越来越了解生之虚无 当我越来越明白 越感到一切界限模糊不清时 我便发现无可言明 一...
    十三纬宇宙阅读 313评论 0 1
  • 020224李纯 作者泽野秋文出生于1981年的日本神奈川县,日本的漫画是全世界都非常有名气的。给你找好房子这篇绘...
    李淳阅读 366评论 0 0