上一次寫到使用spring-security做簡單登錄應用答捕,先補交家庭作業(yè)
如何自定義登錄頁面#####
- 修改
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
等等,寫法都一樣持际。
- 添加
LoginController
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(String err, ModelMap modelMap) {
if (StringUtils.hasLength(err)) {
modelMap.put("err", err);
}
return "login";
}
}
- 添加頁面就是一個普通的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/S和C/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)上盜的別人的一個圖:
- Client向服務器發(fā)起OAuth請求交換
authorization_code
诫给,需要攜帶的參數(shù):-
client_id
:可以理解為客戶端用于登錄的用戶名 -
response_type
:本次請求什么?一般首次請求為code
-
redirect_uri
:認證成功返回的地址 -
scope
:權(quán)限范圍啦扬,指本次授權(quán)獲取資源的權(quán)限范圍中狂,比如只讀,可讀寫之類 -
state
:一般為隨機數(shù)扑毡,可選胃榕,服務器會原樣返回,用于客戶端驗證服務器
這些參數(shù)名稱以及值不是嚴格意義不變的瞄摊,各個認證服務器都會有自己的風格勋又。
-
- 如果是瀏覽器發(fā)起的第三方登錄,比如上述舉例的在知乎上使用微博登錄换帜,輸入微博的用戶名和密碼楔壤,驗證通過之后,則服務器會自動從微博重定向到剛才的
redirect_uri
惯驼,嚴謹一點的服務器還會詢問你是否允許比如知乎請求你的微博個人信息蹲嚣。如果不是瀏覽器比如手機APP递瑰,服務器會直接返回code
。 - 使用
code
獲取access_token
隙畜,這一步一般是在客戶端的服務器(不是第三方認證服務器抖部,比如上文的知乎瀏覽器即客戶端,知乎的服務器即客戶端的服務器议惰,微博即第三方認證服務器)進行的您朽,就是一般來說用戶是無感知的(不是所有的認證服務器都有這一步,只是更加安全而已换淆,服務器實現(xiàn)方式不同,有的是直接可以使用用戶名以及密碼換取access_token的)几颜,一般需要攜帶的參數(shù):-
client_id
:如上 -
client_secret
:可以理解為客戶端的密碼倍试,做過APP的肯定都接觸過使用微博登錄的話是需要在微博申請app_id
和app_secret
的,其實對應就是client_id
和client_secret
-
code
: 第一步請求獲取的authorization_code
-
grant_type
:認證類型蛋哭,也可以理解為本次請求需要做什么县习,這個屬性各個服務器定義非常不同,都不是按照OAuth
標準協(xié)議來的谆趾,各有各的任性躁愿,不過通常意義上都實現(xiàn)了下面五種類型:-
authorization_code
:即本次講解的流程,需要先請求code
沪蓬,再用code
換token
-
password
:直接使用用戶名和密碼交換token
-
refresh_token
:刷新token
-
implicit
:簡單模式彤钟,一般用在JS等直接在瀏覽器里獲取token的方式,當然密碼也是直接放在參數(shù)里的跷叉,盡管加了密逸雹,想想也不太安全(經(jīng) @251be0f15727 糾正請看評論),很少用 -
client_credentials
:無用戶模式云挟,即直接客戶端的服務器單憑自己的client_id
和client_secret
請求資源梆砸,一般用于請求一些服務器的非私密信息,使用極少
-
- 如果不是瀏覽器园欣,還需要加上
username
和password
即用戶的登錄信息
-
上述1帖世,2,3舉得是標準全量流程沸枯,使用不同的客戶端方式可能會有不同日矫,但是大的流程是不變的。獲取到access_token
后就可以使用token
向微博請求個人信息了绑榴,比如昵稱和頭像搬男;也可以使用token
刷新access_token
。
使用GitHub登錄實戰(zhàn)演練#####
先演示如何搭建客戶端彭沼,選擇GitHub作為OAuth認證服務器缔逛,需要在GitHub Settings注冊客戶端
演示所用客戶端配置如圖
需要注意的參數(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)都幫我們寫好了,代碼解釋:
-
addFilterBefore(sso(), BasicAuthenticationFilter.class)
在網(wǎng)站基本認證之前添加OAuth2ClientAuthenticationProcessingFilter
敦冬,上面介紹的OAuth
流程基本都在這個Filter
里辅搬,建議翻看源碼有助于理解 -
@ConfigurationProperties
注解是方便通過配置文件生成所需類,這里因為OAuth
配置比較復雜脖旱,故將properties
文件改用YAML
模式堪遂,具體配置下面講解。 - 注冊一個額外的
Filter
:OAuth2ClientContextFilter
萌庆,主要作用是重定向溶褪,當遇到需要權(quán)限的頁面或URL,代碼拋出異常践险,這時這個Filter
將重定向到OAuth
鑒權(quán)的地址猿妈,本文即/login/github
- 之后向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
clientId
和clientSecret
在上文注冊客戶端的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ù)都可以在對應ResourceServerProperties
和AuthorizationCodeResourceDetails
的源碼找到轧房。
最后,頁面代碼就不貼了绍绘,就是一個a
標簽奶镶,鏈接到/login/github
迟赃,運行成功的話,在user頁面應該可以看到你的github的用戶名
本文到此結(jié)束厂镇,再寫實在太長了纤壁,下文將介紹如何使用
spring-security-oauth2
做OAuth 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
就不贅述了雨席,代碼打了tag
在github v1.2。
PS:上面的代碼其實為了方遍理解OAuth
流程疫诽,spring有個注解叫@EnableOAuth2Sso
(這里用的是它的子集EnableOAuth2Client
)可以一鍵搞定,一行代碼都不用寫旦委,有興趣的可以看一看奇徒。.