3. spring security & oauth2

上一次寫到使用spring-security做簡單登錄應用答捕,先補交家庭作業(yè)

如何自定義登錄頁面#####

  1. 修改WebSecurityConfig
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                .and()
                .formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info")
                .failureUrl("/login?err=1")
                .permitAll()
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()
        ;

    }

解釋看代碼字面意思就懂了,沒什么特殊的屑那,還可以修改比如登錄表單里的用戶名和密碼的名字拱镐,還可以添加各種登錄成功之后的handler等等,寫法都一樣持际。

  1. 添加LoginController
@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login(String err, ModelMap modelMap) {
        if (StringUtils.hasLength(err)) {
            modelMap.put("err", err);
        }
        return "login";
    }
}
  1. 添加頁面就是一個普通的form表單
<form action="/login.do" method="post">
            <input type="hidden" th:value="${_csrf.token}" name="_csrf"/>
            <div class="form-group">
                <label for="usernameInput">Username</label>
                <input type="text" id="usernameInput" class="form-control" name="username" placeholder="enter your username"/>
            </div>
            <div class="form-group">
                <label for="passwordInput">Password</label>
                <input type="password" id="passwordInput" class="form-control" name="password" placeholder="enter your password"/>
            </div>
            <div class="form-group" th:if="${err}">
                <div class="text-danger ">
                    用戶名或密碼錯誤
                </div>
            </div>
            <div class="form-group text-center">
                <button type="submit" class="btn btn-primary">Login</button>
                <button type="reset" class="btn btn-warning">Reset</button>
            </div>
        </form>

這里需要注意的就是表單里有一個隱藏的input叫_csrf是用來防范跨域攻擊的沃琅,詳情查看CSRF≈┯可以在配置類關(guān)掉.csrf().disable()益眉,當然是不推薦的。
以上代碼地址:v1.0

oauth2#####

上面的代碼應該滿足一般意義上的網(wǎng)站登錄,下面簡單介紹怎么使用spring-security-oauth這東西比較復雜郭脂,分兩部分年碘,先講一部分。

至于OAuth是什么東西展鸡,請問百度或谷歌屿衅,官方地址在https://oauth.net/2/
OAuth簡單來說是一種協(xié)議。在我看來就是對上篇文章所說的登錄鑒權(quán)模式的一種補充(當然不是嚴格意義的說莹弊,我只是想簡單明了的闡述一下這是一個什么東西)涤久,大家都知道以前系統(tǒng)是分為兩種模式的——B/SC/S,上篇文章講述的都是B/S模式下的登錄授權(quán)方式忍弛,但是對于C端即Client是不好使的响迂,所以就有了OAuth協(xié)議(來歷純屬虛構(gòu)),那么什么情況下會使用呢细疚?——雖然感覺很神秘蔗彤,但是這個協(xié)議應用卻很常見——幾乎所有的大公司都在使用,比如微信登錄疯兼、微博登錄幕与、GitHub登錄Google登錄镇防、QQ登錄啦鸣、FaceBook登錄Twitter登錄等等這種第三方登錄都是使用的這個協(xié)議来氧,可以看看微博登錄的地址即可:

https://api.weibo.com/oauth2/authorize?scope=email&state=f4dde19fcea5b2ab8c3e682da17a511d&redirect_uri=http%3A%2F%2Fwww.zhihu.com%2Foauth%2Fcallback%2Fsina&response_type=code&client_id=3063806388
oauth2協(xié)議流程######

在網(wǎng)上盜的別人的一個圖:

OAuth2 認證
  1. Client向服務器發(fā)起OAuth請求交換authorization_code诫给,需要攜帶的參數(shù):
    • client_id:可以理解為客戶端用于登錄的用戶名
    • response_type:本次請求什么?一般首次請求為code
    • redirect_uri:認證成功返回的地址
    • scope:權(quán)限范圍啦扬,指本次授權(quán)獲取資源的權(quán)限范圍中狂,比如只讀,可讀寫之類
    • state:一般為隨機數(shù)扑毡,可選胃榕,服務器會原樣返回,用于客戶端驗證服務器
      這些參數(shù)名稱以及值不是嚴格意義不變的瞄摊,各個認證服務器都會有自己的風格勋又。
  2. 如果是瀏覽器發(fā)起的第三方登錄,比如上述舉例的在知乎上使用微博登錄换帜,輸入微博的用戶名和密碼楔壤,驗證通過之后,則服務器會自動從微博重定向到剛才的redirect_uri惯驼,嚴謹一點的服務器還會詢問你是否允許比如知乎請求你的微博個人信息蹲嚣。如果不是瀏覽器比如手機APP递瑰,服務器會直接返回code
  3. 使用code獲取access_token隙畜,這一步一般是在客戶端的服務器(不是第三方認證服務器抖部,比如上文的知乎瀏覽器即客戶端,知乎的服務器即客戶端的服務器议惰,微博即第三方認證服務器)進行的您朽,就是一般來說用戶是無感知的(不是所有的認證服務器都有這一步,只是更加安全而已换淆,服務器實現(xiàn)方式不同,有的是直接可以使用用戶名以及密碼換取access_token的)几颜,一般需要攜帶的參數(shù):
    • client_id:如上
    • client_secret:可以理解為客戶端的密碼倍试,做過APP的肯定都接觸過使用微博登錄的話是需要在微博申請app_idapp_secret的,其實對應就是client_idclient_secret
    • code: 第一步請求獲取的authorization_code
    • grant_type:認證類型蛋哭,也可以理解為本次請求需要做什么县习,這個屬性各個服務器定義非常不同,都不是按照OAuth標準協(xié)議來的谆趾,各有各的任性躁愿,不過通常意義上都實現(xiàn)了下面五種類型:
      • authorization_code:即本次講解的流程,需要先請求code沪蓬,再用codetoken
      • password:直接使用用戶名和密碼交換token
      • refresh_token:刷新token
      • implicit:簡單模式彤钟,一般用在JS等直接在瀏覽器里獲取token的方式,當然密碼也是直接放在參數(shù)里的跷叉,盡管加了密逸雹,想想也不太安全(經(jīng) @251be0f15727 糾正請看評論),很少用
      • client_credentials:無用戶模式云挟,即直接客戶端的服務器單憑自己的client_idclient_secret請求資源梆砸,一般用于請求一些服務器的非私密信息,使用極少
    • 如果不是瀏覽器园欣,還需要加上usernamepassword即用戶的登錄信息

上述1帖世,2,3舉得是標準全量流程沸枯,使用不同的客戶端方式可能會有不同日矫,但是大的流程是不變的。獲取到access_token后就可以使用token向微博請求個人信息了绑榴,比如昵稱和頭像搬男;也可以使用token刷新access_token

使用GitHub登錄實戰(zhàn)演練#####

先演示如何搭建客戶端彭沼,選擇GitHub作為OAuth認證服務器缔逛,需要在GitHub Settings注冊客戶端

注冊測試客戶端

演示所用客戶端配置如圖
localhost配置

需要注意的參數(shù)主要是callback URL,需要和傳遞的參數(shù)完全一致,對應上面的redirect_uri解釋褐奴。

修改WebSecurityConfig

    @Autowired
    OAuth2ClientContext oauth2ClientContext;
...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .anyRequest().permitAll()
                .and().exceptionHandling()
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
//                .and()
//                .formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info")
//                .failureUrl("/login?err=1")
//                .permitAll()
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()

                .and().addFilterBefore(sso(), BasicAuthenticationFilter.class)
        ;

    }

    private Filter sso() {
        OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github");
        OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext);
        githubFilter.setRestTemplate(githubTemplate);
        githubFilter.setTokenServices(new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId()));
        return githubFilter;
    }

    @Bean
    @ConfigurationProperties("github.resource")
    public ResourceServerProperties githubResource() {
        return new ResourceServerProperties();
    }

    @Bean
    @ConfigurationProperties("github.client")
    public AuthorizationCodeResourceDetails github() {
        return new AuthorizationCodeResourceDetails();
    }

    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(
            OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }

這就是主要代碼了按脚,標準流程spring-security-oauth2已經(jīng)都幫我們寫好了,代碼解釋:

  1. addFilterBefore(sso(), BasicAuthenticationFilter.class)在網(wǎng)站基本認證之前添加OAuth2ClientAuthenticationProcessingFilter敦冬,上面介紹的OAuth流程基本都在這個Filter里辅搬,建議翻看源碼有助于理解
  2. @ConfigurationProperties注解是方便通過配置文件生成所需類,這里因為OAuth配置比較復雜脖旱,故將properties文件改用YAML模式堪遂,具體配置下面講解。
  3. 注冊一個額外的FilterOAuth2ClientContextFilter萌庆,主要作用是重定向溶褪,當遇到需要權(quán)限的頁面或URL,代碼拋出異常践险,這時這個Filter將重定向到OAuth鑒權(quán)的地址猿妈,本文即/login/github
  4. 之后向GitHub獲取code包括token的功能spring都幫我們封裝好了音诫,非常方便们衙,如果要自定義,只要重寫對應的接口即可鱼的,就像上篇文章里重寫UserDetailsService是一樣的

配置文件:

github:
  client:
    clientId: fd57ea20f71057e0f396
    clientSecret: 30b73085b6c726b5fb2e0fa8402846b72d86451f
    accessTokenUri: https://github.com/login/oauth/access_token
    userAuthorizationUri: https://github.com/login/oauth/authorize
    authenticationScheme: query
    clientAuthenticationScheme: form
#    pre-established-redirect-uri: http://localhost:8090/login/github
#    registered-redirect-uri: http://localhost:8090/login/github
#    use-current-uri: false
  resource:
    userInfoUri: https://api.github.com/user

clientIdclientSecret在上文注冊客戶端的Github頁面上有占遥,accessTokenUri是獲取token的地址俯抖,userAuthorizationUri是登陸之后詢問的地址,只是第一次會有瓦胎,張這樣:

詢問

使用過微博登錄的同學肯定也見過微博的這個頁面蚌成,現(xiàn)在大家應該知道這個頁面是怎么來了的吧,后面2個Schema是指請求參數(shù)以什么樣的方式跟隨凛捏,有query(跟在url參數(shù)后面)担忧,form(以form的body形式提交),header(放到Http header里)坯癣,none(沒有瓶盛,我也不知道這個是什么意思),注釋的3個是測試回調(diào)地址的示罗,默認是將當前url地址作為redirect_uri惩猫,因為有些服務器是允許多個回調(diào)地址的,這個具體看服務器自己怎么設定蚜点。這些參數(shù)都可以在對應ResourceServerPropertiesAuthorizationCodeResourceDetails的源碼找到轧房。


最后,頁面代碼就不貼了绍绘,就是一個a標簽奶镶,鏈接到/login/github迟赃,運行成功的話,在user頁面應該可以看到你的github的用戶名

kaenry使用github登錄后的頁面

本文到此結(jié)束厂镇,再寫實在太長了纤壁,下文將介紹如何使用spring-security-oauth2OAuth Server,我想這才是重點捺信。另外提一點酌媒,上面的代碼不完整,只支持github登錄(有興趣的同學可以添加諸如FaceBook登錄啊什么的迄靠,改改配置文件和地址就行了)秒咨,本來的admin/admin將失去效果,可以使用ajax提交form的形式代替掌挚,就幾行代碼:

@RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)
    @ResponseBody
    public String ajaxLogin(@RequestParam String username, @RequestParam String password) {
        try {
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
            token.setDetails(loginService.loadUserByUsername(username));
            Authentication auth = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
            return JsonResponse.returnResult(true);
        } catch (UsernameNotFoundException e1) {
            LOGGER.error("ajax login error, username not found", e1);
            return JsonResponse.returnMsg(false,"login.passwordError");
        } catch (BadCredentialsException e2) {
            LOGGER.error("ajax login error, password not right", e2);
            return JsonResponse.returnMsg(false,"login.passwordError");
        } catch (Exception e){
            LOGGER.error("ajax login error. forbidden login status", e);
            return JsonResponse.returnMsg(false,"login.forbidden");
        }
    }

這里后面會搭建OAuth server就不贅述了雨席,代碼打了taggithub v1.2

PS:上面的代碼其實為了方遍理解OAuth流程疫诽,spring有個注解叫@EnableOAuth2Sso(這里用的是它的子集EnableOAuth2Client)可以一鍵搞定,一行代碼都不用寫旦委,有興趣的可以看一看奇徒。.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缨硝,隨后出現(xiàn)的幾起案子摩钙,更是在濱河造成了極大的恐慌,老刑警劉巖查辩,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胖笛,死亡現(xiàn)場離奇詭異,居然都是意外死亡宜岛,警方通過查閱死者的電腦和手機长踊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萍倡,“玉大人身弊,你說我怎么就攤上這事×星茫” “怎么了阱佛?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長戴而。 經(jīng)常有香客問我凑术,道長,這世上最難降的妖魔是什么所意? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任淮逊,我火速辦了婚禮催首,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘壮莹。我一直安慰自己翅帜,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布命满。 她就那樣靜靜地躺著涝滴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胶台。 梳的紋絲不亂的頭發(fā)上歼疮,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音诈唬,去河邊找鬼韩脏。 笑死,一個胖子當著我的面吹牛铸磅,可吹牛的內(nèi)容都是我干的赡矢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼阅仔,長吁一口氣:“原來是場噩夢啊……” “哼吹散!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起八酒,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤空民,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后羞迷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體界轩,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年衔瓮,在試婚紗的時候發(fā)現(xiàn)自己被綠了浊猾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡热鞍,死狀恐怖与殃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碍现,我是刑警寧澤幅疼,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站昼接,受9級特大地震影響爽篷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜慢睡,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一逐工、第九天 我趴在偏房一處隱蔽的房頂上張望铡溪。 院中可真熱鬧,春花似錦泪喊、人聲如沸棕硫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哈扮。三九已至,卻和暖如春蚓再,著一層夾襖步出監(jiān)牢的瞬間滑肉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工摘仅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留靶庙,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓娃属,卻偏偏與公主長得像六荒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子矾端,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容