7 C3 u' y# o, f 简介OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAuth2不是很了解,你可以先理解 OAuth 2.0 - 阮一峰,这是一篇对于oauth2很好的科普文章。 6 j. l1 d/ j: W
OAuth2概述oauth2根据使用场景不同,分成了4种模式授权码模式(authorization code)简化模式(implicit)密码模式(resource owner password credentials)
3 V$ F. |& o Q8 u/ l! [5 H. x 客户端模式(client credentials)在项目中我们通常使用授权码模式,也是四种模式中最复杂的,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式Oauth2授权主要由两部分组成:Authorization server:认证服务
`2 I9 Q& ^6 e) i, P Resource server:资源服务在实际项目中以上两个服务可以在一个服务器上,也可以分开部署下面结合spring boot来说明如何使用快速上手之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。
( M9 z9 ]! Y5 ^/ O! t2 r! S 若不了解 Spring Security 先移步到 Spring Boot Security 详解建表客户端信息可以存储在内存、redis和数据库在实际项目中通常使用redis和数据库存储本文采用数据库。 7 X1 c# Y3 a% E/ n
Spring 0Auth2 己经设计好了数据库的表,且不可变表及字段说明参照:Oauth2数据库表说明 创建0Auth2数据库的脚本如下 ROPTABLEIFEXISTS`clientdetails` 3 G _* Z' `4 G7 K
;
5 d1 U# ^! }% m. Z! x* r: _$ B DROPTABLEIFEXISTS`oauth_access_token`;
H- y/ l9 Y0 W! x! s# x& p DROPTABLEIFEXISTS`oauth_approvals`;" m I1 `/ A; ^+ c% H! k
DROPTABLEIFEXISTS`oauth_client_details` $ W" F6 w% x$ g6 ?/ q6 c
;6 \3 d2 M Z O" L
DROPTABLEIFEXISTS`oauth_client_token`;4 k( I8 v' P( R9 l9 h: m
DROPTABLEIFEXISTS`oauth_refresh_token`;9 h- T, r) \# k; y2 d# x1 v) _/ E0 k
6 P6 b8 y8 K1 F1 Q% x3 l5 s5 t! A CREATETABLE`clientdetails`
$ A, e( |3 x& b (
7 L' |1 D* P- R- i& X# N `appId`varchar(128) NOTNULL,
( i8 H/ C0 f8 V9 p4 ?- Z" M9 O( ]( @, c `resourceIds`varchar(256) DEFAULTNULL,
0 f1 l- m1 O- P! t `appSecret`varchar(256) - U$ A$ Z; u0 A
DEFAULTNULL,' m/ Y; x& N+ t4 L
`scope`varchar(256) DEFAULTNULL,) P) L& @: c' `6 y$ y" N
`grantTypes`varchar(256) DEFAULTNULL,: L* @5 N l p( x6 h6 V5 ]5 Y8 ^8 l* g
`redirectUrl`
/ a' t/ q/ Q+ s( H/ _& d) V5 z/ d varchar(256) DEFAULTNULL,
! g, h" `6 X* b `authorities`varchar(256) DEFAULTNULL,; W. s+ i" R- W- F' A8 D
`access_token_validity`int(11) DEFAULT
/ {- ^5 k. C6 Q4 H. C NULL,
3 d1 V$ U/ O9 K `refresh_token_validity`int(11) DEFAULTNULL,
; q3 F% {( ?# `, [9 t9 U* D `additionalInformation`varchar(4096) DEFAULTNULL
7 O' \' B. i/ i9 k+ ?3 A* @ ,
' L7 n, h# T! z. L6 O, W+ h `autoApproveScopes`varchar(256) DEFAULTNULL,1 g$ Y2 J3 U5 ~2 i' z" } t' o
PRIMARY KEY (`appId`)
& f5 I% ]1 P$ N# C ) ENGINE=InnoDBDEFAULTCHARSET 9 ~: b$ b% w3 V/ d2 K
=utf8;! c0 D0 w3 ~* K4 J# S. R/ J. H
# F% C Q' k4 n6 j
( W. p( j9 i% H# S) M( N
CREATETABLE`oauth_access_token` (
9 x) z3 A' C7 a8 Y; H$ F. I4 N0 W `token_id`varchar(256) DEFAULTNULL,4 t5 t" j3 k5 s
`token`blob,
+ ]: f- T+ \$ @) x8 U `authentication_id` 1 K) o8 E6 N) L7 Z% p
varchar(128) NOTNULL,+ q0 f2 j' ?# R: U
`user_name`varchar(256) DEFAULTNULL,- L) D& V' k2 X( O0 e# K
`client_id`varchar(256) DEFAULTNULL,
. Z5 B! A* t: I
; d2 G" X3 ]# j7 D" b `authentication`blob,. H0 P( K$ d; \
`refresh_token`varchar(256) DEFAULTNULL,
; N6 g) \* l+ t PRIMARY KEY (`authentication_id`)
; o) k* Z# @4 D) k, g$ @* ?+ T: b ) 6 t8 q( t! N. j
ENGINE=InnoDBDEFAULTCHARSET=utf8;6 f7 P$ v# l& k% x% O
$ r& b* V1 A: F; P4 u6 y( g: t4 b CREATETABLE`oauth_approvals` (5 {2 r" r/ x6 }2 j- O9 ^
`userId`varchar(256) DEFAULTNULL,
# c! o$ e# {& _4 `, K) e K0 ?" H
6 h- G2 G2 n5 Z/ B( p. r `clientId`varchar(256) DEFAULTNULL,
h3 g9 z. J: [ `scope`varchar(256) DEFAULTNULL,
7 l& Y# ?7 z. C$ z# f$ h `status`varchar(10) DEFAULTNULL , j. l0 Y0 W/ b) M
,' o0 f4 G, }( w" b
`expiresAt` datetime DEFAULTNULL,5 ^3 @) s& t) B* l5 _0 V" d
`lastModifiedAt` datetime DEFAULTNULL
: Z5 o2 O; n/ N7 E+ D% @+ j t* x ) ENGINE=InnoDBDEFAULTCHARSET + ?' f2 {# s, Y% M. `3 m Y
=utf8;
( n; }6 j! j8 D" h" S |) }& Y: R$ s
CREATETABLE`oauth_client_details` (
) ]" K4 k$ `* N3 G `client_id`varchar(128) NOTNULL,
& R* L# Y9 I% p# V9 q: | `resource_ids`varchar / W" k+ o9 k4 ^1 c! g8 u
(256) DEFAULTNULL,3 A. S5 i, _) r. l
`client_secret`varchar(256) DEFAULTNULL,$ U: ~+ U) ?- T# s H
`scope`varchar(256) DEFAULTNULL,
( F' ]4 h! D. ?' P `authorized_grant_types` 6 B" ~) V* X* \) f$ B0 _
varchar(256) DEFAULTNULL,
5 J" e8 d* |& s. h4 |5 E2 U `web_server_redirect_uri`varchar(256) DEFAULTNULL,9 C3 d% N+ k. S. K$ b1 ^
`authorities`varchar
% f* P5 x7 v5 Y: N7 Y (256) DEFAULTNULL,: S( {/ M3 r* f
`access_token_validity`int(11) DEFAULTNULL,9 ]* i2 i$ B7 r+ q
`refresh_token_validity`int(11) DEFAULT 6 k# B" r% k* q
NULL, f8 a- b7 {3 P- B7 \2 Q
`additional_information`varchar(4096) DEFAULTNULL,
+ I' i) _0 B7 b% ]; I2 q8 p) c0 T$ s `autoapprove`varchar(256) DEFAULTNULL,3 E! D s+ M: m5 I
PRIMARY 3 T, _( F# R9 b" O
KEY (`client_id`)
- X) F3 J. |1 I3 Z9 B ) ENGINE=InnoDBDEFAULTCHARSET=utf8;
; d! q) Z8 S+ O. ]. Q
$ t! M0 C8 K) k CREATETABLE`oauth_client_token` (6 J9 m8 A- N; s
`token_id` 4 u, C3 [" h# ]( n- I: R
varchar(256) DEFAULTNULL,
; f- m- ^. A! O `token`blob,
3 O7 a% r+ j8 ^7 f. B: t c `authentication_id`varchar(128) NOTNULL,
$ ^( v+ _0 B2 ]; f( C `user_name`varchar
' \# q. H9 r7 }' K (256) DEFAULTNULL,
7 a9 T9 ~3 ]; T `client_id`varchar(256) DEFAULTNULL,
1 m. {9 m. m( O& B$ T PRIMARY KEY (`authentication_id`)
/ c3 [4 [3 \$ L: | ) ENGINE + o4 N) Y% f# O- L( R) ~* Q
=InnoDBDEFAULTCHARSET=utf8;
8 r/ v( U' B& V* g% }' X& Q! Y! J$ k+ ?! L m, [
DROPTABLEIFEXISTS`oauth_code`;
; g4 ^! F8 y, n* h1 E CREATETABLE`oauth_code` (& x2 P/ M9 O4 i3 I0 @$ G
`code`varchar 9 t5 f7 W6 f; J1 c
(256) DEFAULTNULL,& c v* Z Y6 {" Y
`authentication`blob
5 ^- X- q8 n/ X% q- U ) ENGINE=InnoDBDEFAULTCHARSET=utf8;
$ D3 o* a: w" l! Z
/ d; |1 U. G' a' |2 E( B; O; A CREATETABLE`oauth_refresh_token` ( K, b+ o6 P L/ w, W' A! g( ? v7 R& {
( {# H' s( P! `: i/ L. |
`token_id`varchar(256) DEFAULTNULL,
( V; ~- U* E4 u; ?& m. m `token`blob, V& X) P: \; v% V
`authentication`blob
# F/ K0 i6 u, t2 X; G# C2 _+ i4 d ) ENGINE=InnoDBDEFAULT
: Y5 U8 X1 e% H2 r3 ~ CHARSET=utf8;
" g8 P/ T% c' `* S5 ^' E 为了测试方便,我们先插入一条客户端信息INSERTINTO`oauth_client_details`VALUES (dev, , dev, app, password,client_credentials,authorization_code,refresh_token。
8 z# N% r9 j9 @/ @9 S% Q+ U , http://www.baidu.com, , 3600, 3600, {\"country\":\"CN\",\"country_code\":\"086\"}, false);用户、权限、角色用到的表如下:
) _, m2 I; C+ [6 r+ E DROPTABLEIFEXISTS`user`;' h# E$ y6 G @: N& u
DROPTABLEIFEXISTS`role`;
# ?8 \+ R E' F! A& p DROPTABLEIFEXISTS`user_role`;
1 k! L( @& h0 Q4 Z DROPTABLEIFEXISTS`role_permission`
) p; |* ^% Z' l; f1 C; x+ r/ n$ s ;
+ u$ R* f u9 G3 U DROPTABLEIFEXISTS`permission`;
- Q7 ]3 Y- R* x- A4 f
, l/ c ]3 `0 l: _7 u3 ~' q CREATETABLE`user` (
9 I% A/ b0 b4 Z% C, ~+ Y) l# } `id`bigint(11) NOTNULL AUTO_INCREMENT,: {/ x8 M) X* m' N& {
`username`
! \5 q0 l, z3 W$ |& r2 B6 n varchar(255) NOTNULL,1 Q- n4 e' e4 f: V, w, \) P7 Y( F# L
`password`varchar(255) NOTNULL,
0 ] D, m7 R" v$ k0 {5 ^/ Z: o PRIMARY KEY (`id`)
- M% ^" H5 T# [% w );% G ^$ u3 N. E$ Y8 z0 D
CREATETABLE`role` (& P9 p( u1 s' h9 z
`id`
4 x) e* a2 X x _ bigint(11) NOTNULL AUTO_INCREMENT,
0 t9 i V G" G+ L) H1 D `name`varchar(255) NOTNULL, s/ y- R# m) b5 i
PRIMARY KEY (`id`)4 g# l$ V# }% m2 t/ U# A M# n
);
3 Y1 A' C% u4 X0 j CREATETABLE`user_role`
4 D" |; P; j7 t; ?4 R2 N (
6 A) L! s _. G3 m' m; T' ` `user_id`bigint(11) NOTNULL,
1 ?' x) h- X9 q, _4 I `role_id`bigint(11) NOTNULL
, U% v# n( e) O5 k );! _1 n/ \. [- [) o( P) E
CREATETABLE`role_permission` (2 R8 n. S) t. t
`role_id`
; h( ^% g* o& }. u6 f. t1 q3 Z bigint(11) NOTNULL,! X1 m0 h }2 G' K7 Q
`permission_id`bigint(11) NOTNULL
1 b: A% O' n7 t; C& ? );1 U5 n H4 o3 p2 n6 ]4 W, t
CREATETABLE`permission` (
: @) }- b" T7 J1 Y4 ?* v- L4 e! g `id`bigint(11) NOT % y# ^$ d1 i# x$ v# l
NULL AUTO_INCREMENT,
: l$ ?# ~% ]. e% K `url`varchar(255) NOTNULL,
: C9 B0 {& d; o6 X% e7 Z `name`varchar(255) NOTNULL,
; i$ ?, _; C* h1 `$ P `description`varchar(255) # a2 Y/ U! ]6 [2 Q* k ?
NULL,
7 m. p* V' X: l# W" G" e1 A2 B `pid`bigint(11) NOTNULL,
6 g3 y% T: u( N3 ]8 c s PRIMARY KEY (`id`)! Q# X% H2 H6 r b' c
);
& l, j, q ?. H5 q5 P, B
+ J& Z! Z9 q) I# A0 @8 v" B INSERTINTOuser (id, username, password) VALUES 0 a% {: j2 G' J# B: O0 T# B" _
(1,user,e10adc3949ba59abbe56e057f20f883e);
8 Y" I$ q9 \. t9 L9 u4 C8 K INSERTINTOuser (id, username , password) VALUES (2,admin
# I$ L6 I! E3 }% l# C& Y& z ,e10adc3949ba59abbe56e057f20f883e);
8 G6 z C# j/ ^5 z7 N INSERTINTOrole (id, name) VALUES (1,USER);3 O* s9 W8 K9 X4 R7 l8 x7 j$ |/ t
INSERTINTOrole (id, name 0 @4 ]" g5 m8 N
) VALUES (2,ADMIN);# X/ {- b& p8 M5 ^: ]) i6 L
INSERTINTO permission (id, url, name, pid) VALUES (1,/**,,0);
& l4 Z- O6 F L INSERTINTO permission (
4 o0 ?# D( c0 `5 _, D; y: j/ N id, url, name, pid) VALUES (2,/**,,0);* {5 l7 v6 N4 J$ O' v0 G+ k$ ^
INSERTINTO user_role (user_id, role_id) VALUES (1, 1);) N+ A% E* ~; r
INSERTINTO
7 B' D- g ?2 [/ j H user_role (user_id, role_id) VALUES (2, 2);& l: I5 i# e3 r! F7 U }' k
INSERTINTO role_permission (role_id, permission_id) VALUES
- L- \. l# q$ z, j, n% U (1, 1);2 d& \( y- y) i1 Q" B9 q \
INSERTINTO role_permission (role_id, permission_id) VALUES (2, 2);项目结构resources
6 I- v) m% q8 }* ]4 r |____templates3 e: r0 I2 V' j" S" b# \; P" s
|
! q4 A5 _& @% B- |8 G* s; Z |____login.html
# y N. d6 O4 T1 P2 `' R ||____application.yml" N% S& d) ]! g S, v! e& T K; r
java
9 d/ O1 R3 O% h& L |____com
3 l- D; e G( s0 j7 n, n | |____gf
0 |! @) W q5 X3 e: W: z2 C) _ | ||____SpringbootSecurityApplication.java
; ~- r5 ^1 f0 A* o l |
: |# U0 ~6 r% D% q% M | |____config( b% U* |! P! V8 Y% l( n
| || |____SecurityConfig.java$ S6 b7 Z* I; `7 ?) G! x+ }" i$ q8 h
| || |____MyFilterSecurityInterceptor.java
2 W4 H ]* ]. U7 [0 n- ~- F | || |____MyInvocationSecurityMetadataSourceService.java
! A) [/ b7 o" [4 @6 } t
5 y/ r( m4 K- D | || |____ResourceServerConfig.java
) @& W! T# K: ~& R$ d* S | || |____WebResponseExceptionTranslateConfig.java6 ~% v. K1 H- |3 R- c
| || |____AuthorizationServerConfiguration.java' o4 t- q& N6 i
! G$ R& o0 t, f4 x$ t( x
| || |____MyAccessDecisionManager.java1 y4 a& o% ]# U+ ~
| ||____entity
: ]) X, Z# M- Y6 F% e* V3 S || ||____User.java
2 R" r$ t7 S* Q# n || ||____RolePermisson.java
5 G* B0 f9 f n# x2 @ |
9 }4 H5 B' r5 } | ||____Role.java
# z" |/ } ]0 O: m) ~* A6 W6 U; A || |____mapper
5 ]' W% C8 q. ~1 }- k: b7 _' Q | || |____PermissionMapper.java
, C' K8 q; b% \. E | || |____UserMapper.java
) y' U- O- Z5 r& c _8 U | || |____RoleMapper.java
! v2 d/ {1 \- T0 ]( G% G
" d1 f) }4 z) m7 f9 E! ` | ||____controller* u4 k$ e( f8 N+ R! _, d) y
|| ||____HelloController.java
: [- d$ {" x! a" G& |' O" p8 C) R || ||____MainController.java
1 l4 Y+ n1 H# \5 @) I- b || |____service
z. o6 k) D) O, @1 Y" A2 l& l4 X | || |____MyUserDetailsService.java
/ ]0 Z2 g! L" e. l: A8 f( } 关键代码pom.xmlorg.springframework.bootspring-boot-starter-security
* N" x N2 O7 r* q& X org.springframework.bootspring-boot-starter-thymeleaf
3 a+ H- N, P" q3 ]% y; I org.springframework.bootspring-boot-starter-oauth2-client
% p+ M7 o: m' O) e Z9 Y! T7 ] org.springframework.bootspring-boot-starter-oauth2-resource-server ) k& h0 r" n2 t- X
org.springframework.security.oauth.boot ) Q( i0 N9 b0 F( t: ^- _0 B
>spring-security-oauth2-autoconfigure2.1.3.RELEASESecurityConfig ) R. ], Q1 M: A7 h
支持password模式要配置AuthenticationManager@Configuration@EnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter
5 c( ]/ g D6 H7 w1 A2 g {
0 P7 ]5 K) O; j$ b, @8 v& u4 }1 P' l9 o) L D
@Autowiredprivate MyUserDetailsService userService;; G& ^0 N! y" m5 D) h
. Q) ]0 B. j. K
0 _' }% P0 e7 Y! v m' J0 `' E @Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)
" F$ O0 J O% O& J) R# C' h throws Exception {
$ e1 f5 T/ m$ G' Z; G% _, e6 p( C
2 E* a1 I; n1 c" d, A( s) Q9 s //校验用户
9 k2 {% d% ^/ Q/ u/ I6 | auth.userDetailsService( userService ).passwordEncoder( new
. X. G; O0 j5 b, X$ N- w7 ] PasswordEncoder() {
- b4 H$ C; T! s, {/ N //对密码进行加密@Overridepublic String encode(CharSequence charSequence){
6 |9 N _, X, O2 o System.out.println(charSequence.toString());; s" t4 z% I+ i2 T+ k
6 I5 i L u8 A4 ^0 g9 b# ^- { return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
- Q2 [% o( E# y/ k6 z; t* R }
" R$ ~: Z- ^$ @) M: i/ Q8 q //对密码进行判断匹配
1 V; g* h3 m: O8 v! u$ T @Overridepublicbooleanmatches(CharSequence charSequence, String s){' p6 z# v; @- p& ?* i% N
String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());' A+ C9 u4 k) P! Q. J
/ A+ ~. z4 R* i) D4 K9 H7 S: G
boolean res = s.equals( encode );
. V3 O( C9 j f return res;- O" U- E# e2 ~% I. v+ [
}
$ s0 S d P; i. s } );
4 Q) R: S! t: s' q& k( Z/ P) L/ Z, ]. m1 X7 m
}
0 S: L0 e& f$ Q' g8 k% Q
! F1 r) Y1 M) q' G& h. N0 d # }* M& \8 Q* o+ v. P1 [
@Overrideprotectedvoidconfigure(HttpSecurity http)throws Exception {9 E/ @& p$ K% E
http.csrf().disable();
' N3 K5 H: D8 t1 `% c1 V( O9 Y http.requestMatchers()
/ g3 a" H. Z4 D$ Q .antMatchers( & Z4 p) d# F1 {$ Z' s& o3 L- N
"/oauth/**","/login","/login-error")
2 L: i$ h. q# K# I3 A .and()
5 n$ z3 \: S; j$ ^8 k) O .authorizeRequests()+ m- ^/ u! [. ]* a- U* o
.antMatchers( ( i0 j2 u) ^2 f/ m
"/oauth/**").authenticated()
2 J5 M! U- a7 `6 c: v, h4 a .and()5 r+ M+ H8 [1 m1 m; S
.formLogin().loginPage( "/login" ).failureUrl( ; F% r" G# y2 G+ h
"/login-error" );" S" s! b u& v- W, q* c5 V
}
) F4 s0 x9 O0 v' M! N' x4 W6 j
1 B: [) V' | N2 `% s
) l. z5 C1 s) Q( u @Override@Beanpublic AuthenticationManager authenticationManagerBean()throws
3 b8 B$ ~' Y9 q$ o Exception{
4 U8 V1 p0 f4 E1 c0 u( s6 f3 ~ returnsuper.authenticationManager();0 E( l5 @5 U1 E
}
: D$ ]! ^& \7 `. L5 H' K
* Y- i- X" |1 S. j- w1 z @Beanpublic PasswordEncoder passwordEncoder
# g, h/ q0 h1 e9 o7 a (){7 |8 Z |: v0 z
returnnew PasswordEncoder() {8 I, g( R! ~4 P# i) K; ^
@Overridepublic String encode(CharSequence charSequence) $ {7 c- z9 l0 k! [. Y) i" B3 D+ G: a
{
+ a2 x! b- a+ L2 @* ~ return charSequence.toString();
! e3 T+ U$ Q% Z W& z+ w }
/ @% a- z/ C/ b% b. P; Z1 D+ L+ _" c& V% M) B0 j3 |$ X
@Overridepublicbooleanmatches * O# b0 G+ J3 y5 |
(CharSequence charSequence, String s){ I) U! I5 @2 c0 Y3 \2 Q3 S
return Objects.equals(charSequence.toString(),s);
8 r6 J k# j7 V/ \7 a w2 K9 C1 S }
# v- f+ p% d. F };4 Y" s, i# w" [ ?
}% E4 e" ^$ ~+ u1 P2 f/ f# c5 Z
' t" N7 y; {- \ S4 ^5 E) ~
3 ^& z# L0 Z: S/ d$ x }
: V% m9 w5 T6 p" Q AuthorizationServerConfiguration 认证服务器配置/**
2 M8 N6 j! g0 a1 k7 r8 k5 j * 认证服务器配置) e+ j& T/ n C, g5 s
*/@Configuration@EnableAuthorizationServerpublic
3 [% T' U7 o9 }7 d3 x; ] classAuthorizationServerConfigurationextendsAuthorizationServerConfigurerAdapter{3 K. { Y9 K3 v$ K
5 X7 a; ?6 G+ ?
; F |1 j+ X3 |0 }8 F /**" O; W2 D( C! u$ ?9 F( y( w4 v
* 注入权限验证控制器 来支持 password grant type
' j- @( t0 `9 N$ ^ */ 8 w9 R* P! J6 q9 Y/ s( ]4 L& m! e
@Autowiredprivate AuthenticationManager authenticationManager;9 Y0 F w7 R8 a, t7 G
) k* U( A- O3 _7 x' M5 u3 l /**9 ?/ [* U+ U; z. `6 K
* 注入userDetailsService,开启refresh_token需要用到; L2 L9 k/ D# q3 j
*/
) W- Z5 [( m/ W @Autowiredprivate MyUserDetailsService userDetailsService;
# M7 L6 V+ U; R; V- y' K
% U' n$ |. Q: o /**9 Y" Z& Q8 M! ]& k6 N
* 数据源: `* w2 ]* l7 x3 U! J# R m
*/@Autowiredprivate 9 d3 J* H( Z5 J2 ~" Z: T3 Q
DataSource dataSource;
9 P+ f: f% E% `, }1 K( U
3 `& e1 k* @( Z8 Z0 d /**
9 Y9 v3 E* ]9 z * 设置保存token的方式,一共有五种,这里采用数据库的方式5 k6 [. p* A8 M) W1 }3 ^
*/@Autowiredprivate TokenStore tokenStore;5 ]$ t+ A6 D6 q* b Y/ w$ _ ?
+ A, Y% c7 C" M- H& t
: E' _% k( i9 o; ` @Autowiredprivate WebResponseExceptionTranslator webResponseExceptionTranslator;
6 i6 r% [; L6 |8 c7 O3 t8 H" ]# J. W4 F, P% J9 r
@Beanpublic TokenStore + { A$ X2 u5 |! ^# k4 j
tokenStore(){0 T! j V9 ?8 }& x
returnnew JdbcTokenStore( dataSource );
, |8 v" l; c7 K$ s6 j+ s, c% I }
5 f/ K$ f' e) m$ g
/ \4 ^2 K, w7 t/ o) \1 | @Overridepublicvoidconfigure
8 W+ Z- p5 v- Z$ j9 U+ [; v (AuthorizationServerSecurityConfigurer security)throws Exception {
4 T) l: A1 B4 {" i( H4 G3 j, u0 E /**5 E* d* s. q! ~( s+ T% a3 E
* 配置oauth2服务跨域( h1 i! |/ J6 E9 T% c0 }5 ~
*/ . W2 e: J# D. p0 Q* r4 o
2 ?! ?( t7 M$ b4 v s* k% @6 m CorsConfigurationSource source = new CorsConfigurationSource() {
* W/ S1 R G$ U# X# Z* h @Overridepublic
9 e& h o ?7 {9 u: K" y( C6 d$ X% b) { CorsConfiguration getCorsConfiguration(HttpServletRequest request){
+ I$ c& z% S9 s/ d CorsConfiguration corsConfiguration = ) @ r J( L* C: i: ]
new CorsConfiguration();4 @# V1 U' r2 X
corsConfiguration.addAllowedHeader("*");' n) p- C* {3 W% k& `1 M5 }
corsConfiguration.addAllowedOrigin(request.getHeader( HttpHeaders.ORIGIN));
- g# n3 i8 E9 K7 u# Q6 Q corsConfiguration.addAllowedMethod(
5 v# N J' r( d# ?3 h7 M1 W* o y "*");
4 ?# U# T; c8 H" v corsConfiguration.setAllowCredentials(true);3 J$ c0 f& Y9 M. V( o
corsConfiguration.setMaxAge( 8 [3 P) ~/ w0 K, ^* `4 [
3600L);
* a6 g ~/ }& m3 I6 J+ u return corsConfiguration;. ^& K7 Y2 H& G( X5 i
}* ~8 v9 T) A4 b. [
};" Y3 c* u+ t! T) H. X! Z8 N
. e8 g) y& p6 ?, }" [ security.tokenKeyAccess(
( P A x$ H! g# x& X5 f "permitAll()")% Z8 f, O$ z: @# R( @1 t$ P- k
.checkTokenAccess("permitAll()")
: \% `/ E3 Z4 K3 X7 L' `+ v# d .allowFormAuthenticationForClients()
5 g6 f$ C5 F4 f$ S, L .addTokenEndpointAuthenticationFilter( $ n/ T" M& x+ K5 |
new CorsFilter(source));
3 p' N, c* T4 j }' U9 g0 D \7 `6 h/ P# L2 z
( h9 V* ^2 A% F9 m: T @Overridepublicvoidconfigure(ClientDetailsServiceConfigurer clients)
3 y. H8 d5 x+ F3 o1 J throws Exception {4 r( \* Y' z8 h' l f
clients.jdbc(dataSource);! m+ K3 E) M1 x; v8 x% z
}5 Y% Q9 ]' c& [% \! G g- A
" v! ~5 o$ T% b; w1 s @Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurer endpoints) # ?: f) B7 V7 z9 q' a- }0 I
throws Exception {
3 w1 f* {" U! ~0 B4 S, K //开启密码授权类型5 N j& V) a2 e8 x
endpoints.authenticationManager(authenticationManager);
5 T; G9 \; T/ c7 B( {* E7 @ ! s4 N8 T i4 E5 i
//配置token存储方式
5 e- f0 V h8 P9 H: U$ f: z! @ endpoints.tokenStore(tokenStore);
1 }$ z' b, y$ p0 W$ ^& P //自定义登录或者鉴权失败时的返回信息0 Q# {8 p1 R2 F2 c2 K" ~% j: D
endpoints.exceptionTranslator(webResponseExceptionTranslator);+ o* i% R4 U2 [( [
# ?9 a( `$ O- \6 c
//要使用refresh_token的话,需要额外配置userDetailsService8 `$ I8 a, [9 K7 o* {8 t
endpoints.userDetailsService( userDetailsService );& V$ u: v* c( x5 u$ ]& ~+ h
) ^4 N+ \7 z' J" D& c
}; y: r% G- R4 U1 o7 _
6 H7 j! E9 o( B3 _4 ^1 F
) s, y( ?4 U a5 F } 0 o3 J6 m0 I/ J0 X/ M5 ] D+ I
ResourceServerConfig 资源服务器配置/**
5 r& O/ d- E4 d& L. T0 L * 资源提供端的配置( p# w1 B+ [3 C w# y' T( k/ g7 t
*/@Configuration@EnableResourceServer- i' j2 z# H$ ~1 K6 `7 f8 j
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
) f: O" c* H7 P% A
' E, D" F2 W4 a0 j L; X
$ l0 x' S* Y+ E+ m. M6 } /**) I5 V' R7 g7 b$ t: W
* 这里设置需要token验证的url5 h" s& D7 Q/ Y4 y9 {# b1 w& U
* 这些url可以在WebSecurityConfigurerAdapter中排除掉,
8 b! V! ?0 l( j) S2 K7 k% t( T6 S * 对于相同的url,如果二者都配置了验证% ] [0 _6 T; Q* o
* 则优先进入ResourceServerConfigurerAdapter,进行token验证。
2 n! z7 Y) l4 E3 Z! d 而不会进行
( `9 T( l+ F9 D * WebSecurityConfigurerAdapter 的 basic auth或表单认证4 F: z/ P9 z# r3 B- B
*/@Override
& Y' b6 b: A9 E* _ public void configure(HttpSecurity http) throws Exception {
% i$ ?, A6 P2 A: u
: {. Z% P. V- o3 {) [; c3 q! a http.requestMatchers().antMatchers("/hi")
1 w* F* ]/ u: f0 I .and()
) s; y0 L0 |0 F9 ], n- x! ^( L .authorizeRequests()
4 \ W4 r9 X" { K
" V' a9 x. _' C- l .antMatchers("/hi").authenticated();1 {/ q' ~: _; [# q# |, ^# X# _
}
. M3 S/ y: d( F! d0 a1 a: J) ~' g/ T( T9 [9 |
! i+ X& C) e$ E9 e3 n }关键代码就是这些,其他类代码参照后面提供的源码地址验证密码授权模式[ 密码模式需要参数:username , password , granttype , clientid , client_secret ]。 3 @* v# r( i3 Q; o' j
请求tokencurl -X POST -d "username=admin&password=123456&grant_type=password&client_id=dev&client_secret=dev"
9 ~+ B$ n" Z G! V2 L http://localhost:8080/oauth/token返回{, w5 y% h! t, V& L* Z% b2 m
"access_token": "d94ec0aa-47ee-4578-b4a0-8cf47f0e8639",
" G, |+ `5 }, h) ~2 }
/ Q" Q: Q6 w1 W3 G! z "token_type": "bearer", I) q( i* j( ~/ ^
"refresh_token": "23503bc7-4494-4795-a047-98db75053374",
: Z' J/ V& m2 |) I0 G- x" r "expires_in" % A& q1 e2 r/ J( v1 c$ n, d' b; D
: 3475,8 v' t1 O+ q. U8 g- f
"scope": "app"9 a1 i( Z( t6 ]( ?
}不携带token访问资源,curl http://localhost:8080/hi\?name\=zhangsan返回提示未授权{% C4 z$ o7 ^2 }5 c2 y3 Y
"error" y% t) F3 j1 i- J) [; m. [ q
: "unauthorized",
1 K1 t, @, s3 P "error_description": "Full authentication is required to access this resource"
m% }$ ~0 ^" g' T. k }
0 C) b: f) u9 M- D# I 携带token访问资源curl http://localhost:8080/hi\?name\=zhangsan\&access_token\=164471f7-6fc6-4890-b5d2-eb43bda3328a
6 B; _- q) r4 t. q, _! i 返回正确hi , zhangsan刷新tokencurl -X POST -d grant_type=refresh_token&refresh_token=23503bc7-4494-4795-a047-98db75053374&client_id=dev&client_secret=dev % S; n6 I# _3 S
http://localhost:8080/oauth/token返回{( G! G, O% }* }
"access_token": "ef53eb01-eb9b-46d8-bd58-7a0f9f44e30b",
+ O) R4 e0 {3 ^! r6 R" [ l+ { 7 E \" j; C8 G' h( t
"token_type": "bearer",+ i# n" l; x* i: D+ B6 l
"refresh_token": "23503bc7-4494-4795-a047-98db75053374",
- Y" J& H8 H; x6 y6 s2 D& L "expires_in"
a: A( O9 A, R0 [ : 3599,
1 {8 U) ~8 y" \0 c. [ E+ q. U "scope": "app"
1 i, f# T2 q2 T4 g. g$ ^+ y }客户端授权模式[ 客户端模式需要参数:granttype , clientid , client_secret ]请求tokencurl -X POST -d 6 \* f' z$ y0 n& Z4 I
"grant_type=client_credentials&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token返回{3 a9 ]8 U- u& d0 D9 k4 k( Y
5 n8 e5 Y. D% K! T; T "access_token": "a7be47b3-9dc8-473e-967a-c7267682dc66",* d7 S6 l8 h6 [ s6 O; X
"token_type": "bearer",
: f) U" V q0 {/ O$ R7 K0 x "expires_in":
" S1 m+ `% V3 }; n1 S" z5 j2 u 3564,
7 L' @ J2 O& \' Q% L6 U1 B "scope": "app"* z" \4 S& u! W8 A7 m
}授权码模式获取code浏览器中访问如下地址:http://localhost:8080/oauth/authorize?response_type=code&client_id=dev&redirect_uri=http://www.baidu.com
7 l- J7 e. L9 ]# z5 ~ 跳转到登录页面,输入账号和密码进行认证: * @0 }) q6 b# `( I" D
认证后会跳转到授权确认页面(oauthclientdetails 表中 “autoapprove” 字段设置为true 时,不会出授权确认页面):
' g# [2 g l3 a5 I 确认后,会跳转到百度,并且地址栏中会带上我们想得到的code参数:
3 @3 F, _7 x: G# ?7 j4 a 通过code换tokencurl -X POST -d "grant_type=authorization_code&code=qS03iu&client_id=dev&client_secret=dev&redirect_uri=http://www.baidu.com"
6 s$ D+ L2 l2 A, ?; H6 B" K http://localhost:8080/oauth/token返回{
+ Y; C* c1 K3 C) S "access_token": "90a246fa-a9ee-4117-8401-ca9c869c5be9",
; u5 o; R" }! B1 S0 O+ W# x - R1 m3 s8 `$ |# b2 \. o
"token_type": "bearer",
- ?& i- p7 d: Z "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
9 t$ s% \: o% a1 b1 E "expires_in" : k4 }0 `1 v5 z: U. r9 R. Q- j
: 3319,
' `& Y# C: G0 D1 Q. e "scope": "app"( ?2 ?" U" v- ]$ M1 p: K; S! G( B- C
}参考https://segmentfault.com/a/1190000012260914https://stackoverflow.com/questions/28537181/spring-security-oauth2-which-decides-security ( ^: Y4 Z% J9 r: B/ B( r, l; i
源码https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security-oauth2 3 f1 ]: I! p! u, q3 _- ]
" F0 Y- ~1 t, ^0 A' F. m
4 l# a c$ ?. L ^' X, b$ l8 B0 r' [/ E% C& r& }! K, s
6 k" _# z5 Q+ o9 l" @
|