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