找回密码
 加入怎通
查看: 168|回复: 0

Spring Boot Security 整合 OAuth2 设计安全API接口服务(spring boot安全机制)

[复制链接]
我来看看 发表于 2023-03-07 02:44:32 | 显示全部楼层 |阅读模式
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
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 加入怎通

    本版积分规则

    QQ|手机版|小黑屋|网站地图|真牛社区 ( 苏ICP备2023040716号-2 )

    GMT+8, 2026-4-4 19:07 , Processed in 0.089015 second(s), 23 queries , Gzip On.

    免责声明:本站信息来自互联网,本站不对其内容真实性负责,如有侵权等情况请联系420897364#qq.com(把#换成@)删除。

    Powered by Discuz! X3.5

    快速回复 返回顶部 返回列表