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

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

[复制链接]
我来看看 发表于 2023-03-07 02:44:32 | 显示全部楼层 |阅读模式
3 d" F& l/ `7 B7 l

简介OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAuth2不是很了解,你可以先理解 OAuth 2.0 - 阮一峰,这是一篇对于oauth2很好的科普文章。

* M, i+ G% h! Y( C0 g

OAuth2概述oauth2根据使用场景不同,分成了4种模式授权码模式(authorization code)简化模式(implicit)密码模式(resource owner password credentials)

2 h6 H, [" \3 S3 [. E

客户端模式(client credentials)在项目中我们通常使用授权码模式,也是四种模式中最复杂的,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式Oauth2授权主要由两部分组成:Authorization server:认证服务

, j# \: v% a2 m! M- p1 r

Resource server:资源服务在实际项目中以上两个服务可以在一个服务器上,也可以分开部署下面结合spring boot来说明如何使用快速上手之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。

4 S5 U/ V# C0 b$ j3 D

若不了解 Spring Security 先移步到 Spring Boot Security 详解建表客户端信息可以存储在内存、redis和数据库在实际项目中通常使用redis和数据库存储本文采用数据库。

+ @% k; I) T3 ^( ?1 |& a6 A

Spring 0Auth2 己经设计好了数据库的表,且不可变表及字段说明参照:Oauth2数据库表说明 创建0Auth2数据库的脚本如下ROPTABLEIFEXISTS`clientdetails`

) }/ }8 w) p; T: H9 `

; ; C5 L; X, z8 k: ]% |- t DROPTABLEIFEXISTS`oauth_access_token`; ! U3 i) X. _4 N: b; C DROPTABLEIFEXISTS`oauth_approvals`;$ @) b" D- d1 B/ J7 G DROPTABLEIFEXISTS`oauth_client_details`

, m% z& Z( p! D' G

;1 U: W/ c+ k$ u3 G3 u' a DROPTABLEIFEXISTS`oauth_client_token`;6 ?6 }% [/ M7 D DROPTABLEIFEXISTS`oauth_refresh_token`; ! Q' @ j" M. F& ?+ d3 E% I% p7 d% n1 H0 L+ n CREATETABLE`clientdetails`

: q( p* o6 q8 a3 I) A7 _

(. k/ [$ r' C- J `appId`varchar(128) NOTNULL,0 i/ _# u: W8 | ]$ m2 n `resourceIds`varchar(256) DEFAULTNULL,* r8 Q( M( m. f" A& Q, r+ @ `appSecret`varchar(256)

$ S3 s/ B6 \) ^; c

DEFAULTNULL, + h" k1 f2 `6 A: g' T2 b4 V `scope`varchar(256) DEFAULTNULL,2 U* O' J# x) q' e( r `grantTypes`varchar(256) DEFAULTNULL,7 x) X) [# D! U6 m- `+ Q7 Q `redirectUrl`

/ E, G6 T, c3 V! J: |$ U _

varchar(256) DEFAULTNULL,6 _; }! J) N3 f2 J b, X- w `authorities`varchar(256) DEFAULTNULL, & b k. d* V- Z ~! F( W( y `access_token_validity`int(11) DEFAULT

& p" p* m6 I" ~

NULL,( j: r2 e% G% o% M, J$ P/ j `refresh_token_validity`int(11) DEFAULTNULL, 7 S! \4 f" W8 O0 b7 ` `additionalInformation`varchar(4096) DEFAULTNULL

/ M0 p: O1 z) m0 p0 k

, % d# E8 C' }& d* m* n4 r* s) i `autoApproveScopes`varchar(256) DEFAULTNULL,; S4 Y( x6 u( Y: p w( l PRIMARY KEY (`appId`) 1 j0 Z, k% ~( V& w* Q I ) ENGINE=InnoDBDEFAULTCHARSET

, \3 t+ i6 H) G2 t

=utf8;3 T" H1 n, A$ c1 X5 g/ u7 B 7 W8 Z! x! ^$ w. d7 `3 f * f3 m: L5 |6 P+ q, f CREATETABLE`oauth_access_token` (1 \/ N+ p% G0 x7 `, D4 C2 V `token_id`varchar(256) DEFAULTNULL,# E# e. c/ a+ R `token`blob,) W* O; \) y, r' Z; a `authentication_id`

9 M. \% I3 v7 i+ |: l$ l* `8 u

varchar(128) NOTNULL, : K/ v$ p6 M7 A1 k- @ `user_name`varchar(256) DEFAULTNULL, 6 s5 @" d8 Q6 I `client_id`varchar(256) DEFAULTNULL, ; E% x0 S- U& P+ L

: h1 U. V9 n$ A7 y. w1 ^

`authentication`blob,. P! b1 Z9 Q) D* R# E, Y" ], l `refresh_token`varchar(256) DEFAULTNULL,+ N5 f& a- O0 \! o2 I0 Z% A. y0 Y* C PRIMARY KEY (`authentication_id`) ) t" q. L+ [" C7 f )

6 h7 b3 ~8 h) x) j; ?* U

ENGINE=InnoDBDEFAULTCHARSET=utf8; * G& i9 N) Y- A. ?" i" i; g2 Z$ r2 J% n$ N' H CREATETABLE`oauth_approvals` (1 f2 ~! V0 j. x; u6 _% f `userId`varchar(256) DEFAULTNULL, 3 x5 m, B' Q4 F; M/ [. T

. p) z3 w) M* F# P; l' m

`clientId`varchar(256) DEFAULTNULL, 9 j2 r/ E6 e; Q) o: N+ e. v `scope`varchar(256) DEFAULTNULL, 7 X+ X) w) |$ C3 Q4 J8 D. X& u% y; e `status`varchar(10) DEFAULTNULL

/ b- }+ D1 c# a' c @

, 1 `! Q) v2 I5 H, m( {( a `expiresAt` datetime DEFAULTNULL, 6 \( g3 v$ o+ r. M, Q `lastModifiedAt` datetime DEFAULTNULL+ J+ h! i: X, T* e$ E! T1 Q ) ENGINE=InnoDBDEFAULTCHARSET

7 |8 G; y' G+ l7 s/ d$ v

=utf8; }* q7 J7 s ] g' H1 b* f 5 F& z$ ]/ A3 x4 c CREATETABLE`oauth_client_details` ( 1 V8 c% |' {6 ~7 C Q* T1 y `client_id`varchar(128) NOTNULL, ) Y# W$ ^, {( h% @( H2 s( X' K `resource_ids`varchar

& ^7 G$ I+ @- N' Q# S

(256) DEFAULTNULL, " i# f8 F8 ?0 Q `client_secret`varchar(256) DEFAULTNULL, + s( Z7 ~. `( c `scope`varchar(256) DEFAULTNULL,7 @5 i8 t" y f' h( s* j `authorized_grant_types`

5 w) s: e( A" x) Q/ d' ^7 ~+ x) Y

varchar(256) DEFAULTNULL, / D6 I4 m/ g+ E$ A& s: a- Z `web_server_redirect_uri`varchar(256) DEFAULTNULL, 0 a1 Z" E% {2 t: S `authorities`varchar

& ^" K9 q1 E' A) X

(256) DEFAULTNULL,! S4 s4 U, h* M* b/ M5 T" J `access_token_validity`int(11) DEFAULTNULL, ) s6 {; X& O( R6 v2 D `refresh_token_validity`int(11) DEFAULT

' e4 R; r! C3 z2 S! {9 t+ Q. Q

NULL, ; G- @" ?% B' q; j: z0 U. h `additional_information`varchar(4096) DEFAULTNULL, ) ^5 i0 o5 ^" R, ~8 `7 S( ~ `autoapprove`varchar(256) DEFAULTNULL, ' V: S4 @; i& Z, A8 y1 o9 f Q& j PRIMARY

4 y5 v+ ^0 Y p" u2 s

KEY (`client_id`) ' N: K, X; K- \, q: J ) ENGINE=InnoDBDEFAULTCHARSET=utf8;3 d% }1 \5 Z* b & S$ o9 F- l- P; [( v+ G U" x# R CREATETABLE`oauth_client_token` (6 _) T: q9 n a# J& w `token_id`

0 M* [6 ?5 B! O7 T! k

varchar(256) DEFAULTNULL, , ]* A# s& A' D6 [! Z# k6 s& D `token`blob,9 i& T! T" g8 `) y } `authentication_id`varchar(128) NOTNULL, 5 M4 z6 W- \3 n `user_name`varchar

* ?( ] ~& {3 J) m% [, B5 ^

(256) DEFAULTNULL," e) h% i% x5 X2 p9 s+ F `client_id`varchar(256) DEFAULTNULL,( d, j5 P' G8 L$ X- | PRIMARY KEY (`authentication_id`)+ B. l$ B" h& R3 V3 @ ) ENGINE

' s4 b2 p4 x, w- F

=InnoDBDEFAULTCHARSET=utf8; % ~6 Z4 A f% a! f V* P. i& C2 B3 p- [7 R- r$ u DROPTABLEIFEXISTS`oauth_code`;) I5 k2 D5 _# I: M' U2 t' Z$ _ CREATETABLE`oauth_code` (- R4 \# v# w8 ~. e9 M `code`varchar

5 ^, ], W8 k6 l+ y" x) i+ F# T- i

(256) DEFAULTNULL," F! A8 @) u7 { `authentication`blob% T) g( C" o( T# g2 m* B! W3 f ) ENGINE=InnoDBDEFAULTCHARSET=utf8; , x; G9 Z3 a- t# |6 L ' U2 h; t5 z. w" X9 S CREATETABLE`oauth_refresh_token`

& J$ d% R9 [2 v/ V0 r

(4 q2 _% z6 F6 k5 A' E1 \ { `token_id`varchar(256) DEFAULTNULL," u/ y& }. N, Z `token`blob,0 d0 x& H, s! l. p `authentication`blob 8 ]) B* [1 m5 a% A5 O3 `+ t$ i ) ENGINE=InnoDBDEFAULT

) R6 _( U: c7 p9 K1 {/ G/ E. o

CHARSET=utf8;: H5 V4 N: }8 q( u 为了测试方便,我们先插入一条客户端信息INSERTINTO`oauth_client_details`VALUES (dev, , dev, app, password,client_credentials,authorization_code,refresh_token。

" i. l# v+ e4 B, q1 o

, http://www.baidu.com, , 3600, 3600, {\"country\":\"CN\",\"country_code\":\"086\"}, false);用户、权限、角色用到的表如下:

) ?! B7 p# P0 N& C; }; P0 Q

DROPTABLEIFEXISTS`user`; % L4 ^9 b: u' I# A6 | DROPTABLEIFEXISTS`role`; . D; H- \; ?4 h. |7 f! B DROPTABLEIFEXISTS`user_role`; , ^/ A8 ~" j3 A+ Y! z' P DROPTABLEIFEXISTS`role_permission`

. j ~! U- n: \ O1 I2 C, R& @

; $ ]. N5 B' b6 E0 M+ ~2 V# ^' {0 M0 B DROPTABLEIFEXISTS`permission`; ( ~% v) l) O0 ~6 z `+ o ' g, Y/ T/ r% d% D CREATETABLE`user` (. `8 D# q; I( k8 A, K `id`bigint(11) NOTNULL AUTO_INCREMENT, - X0 Q7 \' @1 n; E `username`

! C" d1 @. A, |; D

varchar(255) NOTNULL, _$ J C& j0 p+ e( b `password`varchar(255) NOTNULL,9 r( R* E" b9 m( e* i PRIMARY KEY (`id`) & [& l$ O- a2 \9 @! p ); & B* e5 M2 S. I1 O' o% V CREATETABLE`role` (0 i/ G; |2 l2 W& b }" [% N7 W `id`

) t' W/ T" \: K$ |0 c

bigint(11) NOTNULL AUTO_INCREMENT, , {4 I, u; }2 P; o& M$ P `name`varchar(255) NOTNULL,% D2 c! E: ^/ ?* a PRIMARY KEY (`id`) $ p+ p0 x5 d9 M ); / P) E2 T u6 p+ K' v/ z CREATETABLE`user_role`

8 h2 M- Z3 X6 m9 j* i3 O5 t

( 3 w% H( y2 K1 X& a% L m `user_id`bigint(11) NOTNULL,; y! j! n' x) i) i" t" G9 x" d0 J4 F `role_id`bigint(11) NOTNULL2 F8 q$ i# j7 W; X. _/ p ); 4 M& R7 ^7 K* w T+ f2 m! G( o! R CREATETABLE`role_permission` (% j1 g; i/ b/ o/ Y$ g9 j8 q. d `role_id`

* z7 {: M4 u) S

bigint(11) NOTNULL,7 W* G/ Y) @. L" p4 m% G `permission_id`bigint(11) NOTNULL + @* j; x% s! q& m );4 v4 [3 G% T/ m CREATETABLE`permission` (* |4 Q( X" Y' x0 c% j `id`bigint(11) NOT

8 N7 J/ o6 O( G" j7 _

NULL AUTO_INCREMENT,0 ? t2 `5 N& P8 h `url`varchar(255) NOTNULL,) g( p. z) a. Z+ V& A* X2 o `name`varchar(255) NOTNULL, ' k- ^$ I! q. f$ U, h9 o* X' g5 k& S `description`varchar(255)

( c# }' D: v" i% q, N r

NULL,- `4 K! z" l' I8 g* P2 @ `pid`bigint(11) NOTNULL,. T& ?% l' e/ L6 `' L# L PRIMARY KEY (`id`)2 W; A; N: |) [8 E6 c! m ); 7 L G$ V; c4 y) j8 z4 b 9 v! @: k# V0 `( F! A INSERTINTOuser (id, username, password) VALUES

. J1 n7 U2 K9 `( W F& c

(1,user,e10adc3949ba59abbe56e057f20f883e); ! U/ u8 i- X Z! x9 ~& S INSERTINTOuser (id, username , password) VALUES (2,admin

! B* ^( s2 s# p* L! k

,e10adc3949ba59abbe56e057f20f883e); 7 { K2 ?0 m6 E) Q, x! E6 S1 T4 d INSERTINTOrole (id, name) VALUES (1,USER); 7 s' l! X# U* V4 M' n INSERTINTOrole (id, name

! p9 s# t0 ~1 _6 } x2 X; K

) VALUES (2,ADMIN); 2 Y, c( q; y k4 E4 K INSERTINTO permission (id, url, name, pid) VALUES (1,/**,,0);( I) h5 N! e6 S3 N, ?" L" i INSERTINTO permission (

) `- @- D+ e3 i: c3 B

id, url, name, pid) VALUES (2,/**,,0); ! g- J) m0 u! m$ f INSERTINTO user_role (user_id, role_id) VALUES (1, 1);/ E. r! T9 h- V* B P5 _" S INSERTINTO

7 R* B8 t, e4 c

user_role (user_id, role_id) VALUES (2, 2);* h& Y: ^3 m4 j: z. ^. J INSERTINTO role_permission (role_id, permission_id) VALUES

2 G" v/ |0 _) V0 i( H, M9 n$ j# A

(1, 1); + I: ]1 { V% B- N- P( ^8 S INSERTINTO role_permission (role_id, permission_id) VALUES (2, 2);项目结构resources6 Q+ i) [! n9 d, Q: \6 r0 f |____templates 7 D! b( E9 \6 u5 B; J* @ |

: ]" X6 v' A* r" Q

|____login.html- c" H& ~3 p7 g6 x: i+ I ||____application.yml/ E: X1 Y1 o1 T4 f i0 z java1 {+ F' ]1 r% Q3 D5 [# X/ ~ |____com # m# H j M7 `* ~- l% H! p6 y | |____gf $ A$ G9 \* Q, `$ l | ||____SpringbootSecurityApplication.java6 C& p/ {: P. c2 S) q' r# g |

" G; S, O) p" i1 ^% a& i5 J

| |____config6 L! M: h! h* B; @. S1 s% d( f; m | || |____SecurityConfig.java r0 ~2 s2 l, u8 R7 ~5 l | || |____MyFilterSecurityInterceptor.java % @6 {) `/ t, Z4 `6 K% @ | || |____MyInvocationSecurityMetadataSourceService.java) @: H8 ? H0 H& Q' M, E9 O

- e9 J3 V* ?+ S) D

| || |____ResourceServerConfig.java 3 O4 M& a. B$ F" y | || |____WebResponseExceptionTranslateConfig.java4 I: f) i9 w. c | || |____AuthorizationServerConfiguration.java 6 @) H4 v% b9 ]% Q* T8 L2 a& R" X. l

7 v7 l/ g7 l$ p

| || |____MyAccessDecisionManager.java5 n: m6 `6 f0 u! I1 Q- F9 x+ ^ | ||____entity$ Q b+ @+ p7 i# l || ||____User.java 0 x5 v3 M5 E) P0 M! x2 J || ||____RolePermisson.java 0 g& o2 p. [* j0 @ |

$ k% ]2 F! j L( T2 a9 D) J8 j

| ||____Role.java9 t2 A a, C7 ^3 g% i || |____mapper* X2 {1 x4 m" I W7 ~6 e | || |____PermissionMapper.java * ~- I2 \6 D$ u' X- \+ ~2 |$ s | || |____UserMapper.java % Q$ v# T9 F& @9 f | || |____RoleMapper.java . x' ]" _6 _/ a. k8 j

9 U2 e; L! ?" V& o, n

| ||____controller5 s$ P4 M- J: H) C* u& x1 _' I || ||____HelloController.java ' n) q0 C$ N% @" B @ || ||____MainController.java# j; ]/ }. ]% [5 ]) D6 }1 T || |____service 8 a! |" @1 @0 P4 W0 k | || |____MyUserDetailsService.java

& ]) @- M' ~" I1 ~

关键代码pom.xmlorg.springframework.bootspring-boot-starter-security

1 f# m6 ^% ]5 O

org.springframework.bootspring-boot-starter-thymeleaf

3 |1 B) S2 V% \: C6 U9 R

org.springframework.bootspring-boot-starter-oauth2-client

6 r3 v* e2 L: p! w

org.springframework.bootspring-boot-starter-oauth2-resource-server

8 |7 Z4 p# O8 M+ p

org.springframework.security.oauth.boot

: _2 d9 R8 F. [' @. |5 }; t

>spring-security-oauth2-autoconfigure2.1.3.RELEASESecurityConfig

8 K5 [( I2 N& M* j

支持password模式要配置AuthenticationManager@Configuration@EnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter

" W4 g' [7 O& y5 G

{ % c; }8 i5 }9 _9 q# c$ m+ X3 w 9 N7 w3 V( q4 c3 |4 R; F @Autowiredprivate MyUserDetailsService userService;) M/ _/ ~4 g; b ' t( J( e! c5 K8 u E# e( w! h @Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)

& L# L; [- x4 p4 V' ]

throws Exception {, r# I( L2 Y* }: O2 X + X, \! m+ F* z& S; l- {4 ]. Z% t //校验用户 4 u* M2 ?8 x4 a9 u auth.userDetailsService( userService ).passwordEncoder( new

) ]& z5 f% ^9 T" r/ q0 _. x- n

PasswordEncoder() { 1 w) u2 z2 x- \1 _8 h( M. t //对密码进行加密@Overridepublic String encode(CharSequence charSequence){% m1 ?; j# z( P* G2 X System.out.println(charSequence.toString());) Q. }1 ]8 H2 k% E& r* w

% m3 D* J- l4 E# G- G

return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()); % d# k* c" J" z* r3 I# J4 N M } & \6 V/ m) R1 i: A& z6 V+ {6 t //对密码进行判断匹配

5 m) [" Z, n% w1 c# a

@Overridepublicbooleanmatches(CharSequence charSequence, String s){ * ]4 o0 G& k; H% [* | String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()); # `1 Y) i+ e5 d+ y7 `

. N( Y- i1 i6 w4 \9 K

boolean res = s.equals( encode ); % W7 C7 k. ?3 z1 |8 [: K return res; ( ?5 s: g6 z/ L' R3 Y } * n) |, X: s! X' d9 M2 k* ^ } ); 5 G( v+ q* f2 m6 ?# p% o% H" @ ' u1 j2 M1 N) C& v1 { }+ b; M- H* p1 l( R4 m . u H3 p" T: }) a

: s# p+ N7 d; E, H- x3 o# L

@Overrideprotectedvoidconfigure(HttpSecurity http)throws Exception {" E6 ?2 s2 {. K* p- i7 `+ z http.csrf().disable(); ) s& S8 }& }8 J* {8 o http.requestMatchers()4 J1 o: {; K( X, ?2 r8 X) ~ .antMatchers(

2 b) d6 X- b: Q* D# g# O* n

"/oauth/**","/login","/login-error"), a k$ D p: I6 |9 Z .and()+ T3 k+ }' E; x. \0 f+ _5 l .authorizeRequests()& |& w/ h* V$ d( F# Y4 q .antMatchers(

5 }- h6 l2 o! x3 R

"/oauth/**").authenticated() . l( j8 t. u- g: G1 Z7 ? .and()2 j: R* }# t2 a5 B2 `/ ` .formLogin().loginPage( "/login" ).failureUrl(

: k: R8 x2 L6 t1 p1 n

"/login-error" );% m5 I5 H# }3 |8 `& [ }7 p1 |( Y+ R4 i+ ^9 \% y 1 u* I+ L/ _. `, R , ?- b" V& s$ m: V* `0 f- s/ U @Override@Beanpublic AuthenticationManager authenticationManagerBean()throws

9 Z* w& y/ H. Q) R! x+ y1 e1 H1 `; b

Exception{8 d8 Y) C& k. s% T/ u returnsuper.authenticationManager();; C4 `. K' N9 `4 N }- ]" g( t5 L# r 6 @8 ? v' O% P! h$ Q2 L9 w @Beanpublic PasswordEncoder passwordEncoder

/ ]" w ~/ K, B3 a1 \# T

(){4 u% L3 o4 s& Y) H+ Y returnnew PasswordEncoder() {+ [) `8 P+ `! o @Overridepublic String encode(CharSequence charSequence)

' b0 B& D! H$ s: L6 M6 j

{ 1 q) S# e) k9 r" m1 N. S- J& _ return charSequence.toString();! s- H$ m3 z: h4 M% z }$ P7 H0 W9 R4 f 0 W+ Z( y* j. B) p2 t* Y# f0 G% U @Overridepublicbooleanmatches

/ N9 A4 B: e9 {: r$ G$ Q& c

(CharSequence charSequence, String s){ 4 ~) q3 r v0 R* k! j; F return Objects.equals(charSequence.toString(),s); 1 v, \ c4 ?% \4 C4 V! T# D }7 W F. J# J* u8 h$ t };& W0 a; P; w, u6 C }. g* E- O3 f1 E8 M' U2 R 1 J4 s' |9 h2 r6 @/ U% Q 2 j9 n* K Z5 S* @ }

f O1 U7 X6 I, c# J8 s

AuthorizationServerConfiguration 认证服务器配置/** * }" O0 t9 y" y' a7 K * 认证服务器配置/ r/ T4 b) Q W4 J& d$ ] */@Configuration@EnableAuthorizationServerpublic

$ r0 T! E; r; o4 p& S' Q. v* B

classAuthorizationServerConfigurationextendsAuthorizationServerConfigurerAdapter{; K6 h1 n& n" p: v, W & U; M! T3 R- I 1 a2 x1 K. P0 c V8 Q /**4 j) T2 q0 g/ D0 @% r * 注入权限验证控制器 来支持 password grant type, a9 p0 M3 j9 Q$ i% }( T, n0 ~5 T */

+ d2 s, ]6 {, h) ?0 t$ L# H$ h

@Autowiredprivate AuthenticationManager authenticationManager; * K' x" ], ]2 e" q9 E* y , a& l$ i0 ?, W& T1 e5 o7 D( I; a /**. N/ V- `: U- b: }8 U* a, L# O E * 注入userDetailsService,开启refresh_token需要用到! ~3 C6 F- p! P8 V$ w5 P! Y */

' i1 I ~1 g4 Q+ W; Q% m* H( Z# R

@Autowiredprivate MyUserDetailsService userDetailsService;- l% q) w% ]8 J 7 S# R, E2 z7 q/ H' b /**9 h- g2 ^: h8 f, w/ L2 m9 C * 数据源6 x6 x. A2 P9 K) F */@Autowiredprivate

. i) h4 d' Q& L- N( T" O+ [3 H5 r

DataSource dataSource;- {- G* ?; e+ y. ^ ' @# b4 U% C- j, A /**) I# _8 a0 c0 ] * 设置保存token的方式,一共有五种,这里采用数据库的方式 8 J: s' b" p; c5 ]" c! x */@Autowiredprivate TokenStore tokenStore;9 F. U) n$ t' ?7 D9 L5 b ' L# K! k6 C* w! l( a/ U& o) C

# _# K. Q/ v o8 F

@Autowiredprivate WebResponseExceptionTranslator webResponseExceptionTranslator;& B/ _2 _1 x1 s2 X: q, I - o" i0 y0 u' B" m1 ?- A6 { @Beanpublic TokenStore

' W. e+ J! ]& x: G

tokenStore(){ # a" T" c" Y( j returnnew JdbcTokenStore( dataSource );6 R0 s, ]( I- G8 k1 S! Y* v! m1 l }2 r5 o- w3 x- z9 M+ a5 S ! |4 }: [/ C# H: j1 ] @Overridepublicvoidconfigure

9 d' n( \- `/ K; ~" `4 A% a y/ M

(AuthorizationServerSecurityConfigurer security)throws Exception {' X0 B& o, _) K" b. K9 w3 p2 [7 U /** ) U0 S+ @! ~1 c6 N$ ` * 配置oauth2服务跨域 , F' k9 J H- E2 I+ u' Q6 O */

1 L7 a5 q1 F/ D2 p, W0 r

, ~4 q/ i- S v" @3 O, \ CorsConfigurationSource source = new CorsConfigurationSource() { + W& ?; s! d, H3 ` q, D9 N @Overridepublic

! l% O+ v% X4 d0 @

CorsConfiguration getCorsConfiguration(HttpServletRequest request){( G ~8 ^; s: m CorsConfiguration corsConfiguration =

' E0 U7 Q4 \8 D3 o7 r7 N- d

new CorsConfiguration(); ; _4 a; O+ p" C& Q2 k corsConfiguration.addAllowedHeader("*"); 1 s6 S/ f2 T' u# F corsConfiguration.addAllowedOrigin(request.getHeader( HttpHeaders.ORIGIN));" T' w S$ p* a6 u corsConfiguration.addAllowedMethod(

/ ?" V2 p5 ?$ m* x8 }9 \* J3 ]

"*");/ P, M+ @! P3 N0 V$ u' Y H; m corsConfiguration.setAllowCredentials(true); ' k2 n) f" _5 }. h5 O corsConfiguration.setMaxAge(

# N% @" s& J& z7 y, e6 G |

3600L); 8 E5 E$ a) l& z+ o* r% B3 W return corsConfiguration; & q3 Z9 o0 G0 P% d' j' S9 a } % ~3 P* j$ D2 @( E* ~ }; N& F( S& r/ O q/ D) H- B) E' l* q) R security.tokenKeyAccess(

' n( c- T% w; B0 v$ B# Z7 `5 a

"permitAll()")& b! w$ w, {! g+ S .checkTokenAccess("permitAll()")' Z7 j" m) h4 [* Y- y: Y .allowFormAuthenticationForClients() , U' E% ?+ A9 ~0 F g; b .addTokenEndpointAuthenticationFilter(

/ M1 u, s7 d; J, y& e, m

new CorsFilter(source)); 4 m9 F0 Q$ H- Y: g; k& ?* W5 G }# u- ^ [& r3 H8 D4 U5 j 0 y( m5 z; \6 [$ r$ b+ D0 ~ @Overridepublicvoidconfigure(ClientDetailsServiceConfigurer clients)

- t% }8 x4 p. a) S0 \6 Q+ t

throws Exception {+ o3 B' ^) P9 ]* T' W w clients.jdbc(dataSource);+ M0 m9 | f7 j- f$ l1 \ }3 A: t* k+ A/ l0 a' @ 5 B: W; b3 @) t; t% N' ? @Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurer endpoints)

# ?" R, s; q2 X' r7 ` D$ l( ]

throws Exception { - ~; h* l; t5 T2 c8 `. ` //开启密码授权类型 ' E# I0 o* _( W* \9 p7 W( d; @ endpoints.authenticationManager(authenticationManager);* ?* Z1 \) ^" y$ O$ w

: ^7 [; n/ Q! _5 V

//配置token存储方式 * M, q# i9 m+ @: C3 X' B endpoints.tokenStore(tokenStore); : C1 ?( A- e3 v //自定义登录或者鉴权失败时的返回信息& E. ]% ~$ {) T% ?$ x0 V8 v endpoints.exceptionTranslator(webResponseExceptionTranslator); / f/ Z6 Z6 W1 l7 c6 |8 g6 S

/ Y5 \$ p+ ^% a

//要使用refresh_token的话,需要额外配置userDetailsService& Z8 I0 o! [" w+ ?" i; t0 k endpoints.userDetailsService( userDetailsService );1 C/ G/ A6 D* w- q# V" H [" u: j6 q; D ) s, }2 L9 a8 p8 o( S) j! [ }8 h7 Q/ @3 u! ?' d & m4 z9 P) w5 v6 F8 O1 ] * Z0 Y0 e/ i4 ]/ }' L }

0 W2 f9 D" u, ?5 V P8 L! a

ResourceServerConfig 资源服务器配置/** 8 k9 ]4 W& K6 A8 O% { * 资源提供端的配置 0 F7 Z8 \# O- Z" N$ E7 n */@Configuration@EnableResourceServer% ] v! N/ v# A7 [: H public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 5 ], U8 ~# U2 l1 U0 e) v% t4 @& s9 l. u0 _! _7 j4 r+ i

# x; x' `+ k: p) x

/**$ g6 [/ a j) n7 v! F0 V * 这里设置需要token验证的url 7 A; e' `5 h+ Z * 这些url可以在WebSecurityConfigurerAdapter中排除掉, ! j- v% ]/ c, h, I7 B * 对于相同的url,如果二者都配置了验证 $ `! P3 ]' Y. f- I" f+ ]+ E( M * 则优先进入ResourceServerConfigurerAdapter,进行token验证。

2 |' K0 T9 d& G# ]: |! Y/ ?( M

而不会进行 ; n: A& N* U5 {2 ? * WebSecurityConfigurerAdapter 的 basic auth或表单认证, o% F0 j* X3 r# n- E */@Override3 D# s4 D; F* O public void configure(HttpSecurity http) throws Exception { % T. z* E. {) }

$ \0 P1 s* U' M% n! r: ^$ ]

http.requestMatchers().antMatchers("/hi") * j6 R/ ]/ ^- U .and() 9 c* t+ K3 n2 ?& H* b .authorizeRequests() 3 c. M0 }; m/ c- `% ?+ W3 {- q

7 p: e( q. U, S. k5 q

.antMatchers("/hi").authenticated(); % X$ [$ P* f$ u2 M }1 N# Q4 H; H9 m8 ?2 g: q 1 ~# i/ l* B0 A+ i0 a( f9 n * |) x& s% k) W/ T, |, p$ V }关键代码就是这些,其他类代码参照后面提供的源码地址验证密码授权模式[ 密码模式需要参数:username , password , granttype , clientid , client_secret ]。

h4 X5 |8 t& q& f1 |9 Q

请求tokencurl -X POST -d "username=admin&password=123456&grant_type=password&client_id=dev&client_secret=dev"

6 ^9 O, |: G4 x. F) ]

http://localhost:8080/oauth/token返回{ 4 m5 t- j4 s* T+ z! X "access_token": "d94ec0aa-47ee-4578-b4a0-8cf47f0e8639", W9 W* p) @1 @

1 O3 v, T' j e' Q2 O5 n9 O

"token_type": "bearer", , v* n3 E3 e5 e "refresh_token": "23503bc7-4494-4795-a047-98db75053374",8 N0 ?' u- l2 x- E& L; U) J1 x "expires_in"

: b" |1 b9 q3 s4 V" [ S+ o+ w I

: 3475,7 \" D# W& F. a5 N "scope": "app"+ J6 d" Z" G, q$ k9 N2 P1 x3 [# M) \* u }不携带token访问资源,curl http://localhost:8080/hi\?name\=zhangsan返回提示未授权{# J* Q3 {; o3 C3 [" i+ M "error"

x$ `1 D1 d9 G+ d# d G+ X2 k* ~

: "unauthorized", 4 r. q1 }' t) u' f3 R "error_description": "Full authentication is required to access this resource"' _5 d; ^! P3 `2 P( ]" r1 B }

; d4 ~8 X7 ~" y2 n: O

携带token访问资源curl http://localhost:8080/hi\?name\=zhangsan\&access_token\=164471f7-6fc6-4890-b5d2-eb43bda3328a

$ @5 n' m# Z/ p0 n9 c' x

返回正确hi , zhangsan刷新tokencurl -X POST -d grant_type=refresh_token&refresh_token=23503bc7-4494-4795-a047-98db75053374&client_id=dev&client_secret=dev

8 ~- O; v" P1 m" |% {4 B' j

http://localhost:8080/oauth/token返回{5 c. ~$ _" q( y# u% m, N# a "access_token": "ef53eb01-eb9b-46d8-bd58-7a0f9f44e30b"," N$ N" S" g" N6 p

9 Q. S4 D# I7 T

"token_type": "bearer", ; b- p8 O1 m2 B G" r( v "refresh_token": "23503bc7-4494-4795-a047-98db75053374",6 t9 E- b1 ~ h T "expires_in"

+ \- T) P/ q/ Z) G* B$ p, R* ]% P

: 3599, 9 p$ A; v5 y9 ]0 U- w( X3 r3 N3 a "scope": "app"6 K, Z: W0 j; n9 y/ Y5 ? }客户端授权模式[ 客户端模式需要参数:granttype , clientid , client_secret ]请求tokencurl -X POST -d

( w8 z' ~2 E+ z, z& `

"grant_type=client_credentials&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token返回{ " h3 |' X7 y, \5 Y

8 P& D/ x5 i0 Z) k' f+ b. k% |

"access_token": "a7be47b3-9dc8-473e-967a-c7267682dc66",2 c2 K; F3 i+ O9 D& G r7 S "token_type": "bearer",! B9 t' ~' J9 q0 h! y "expires_in":

; M! b8 K8 n0 z" e

3564, ) z, I- i, k: A. x }3 B "scope": "app"& M, I% t4 Q0 z8 \* L, ? }授权码模式获取code浏览器中访问如下地址:http://localhost:8080/oauth/authorize?response_type=code&client_id=dev&redirect_uri=http://www.baidu.com

; V. d. h. b. ]

跳转到登录页面,输入账号和密码进行认证:

: X0 ^& a9 w, Y- C4 P

认证后会跳转到授权确认页面(oauthclientdetails 表中 “autoapprove” 字段设置为true 时,不会出授权确认页面):

* A; B- S* d6 U' X+ ]3 l9 u7 s- b

确认后,会跳转到百度,并且地址栏中会带上我们想得到的code参数:

4 ^% a# P* r. M! V, b7 f

通过code换tokencurl -X POST -d "grant_type=authorization_code&code=qS03iu&client_id=dev&client_secret=dev&redirect_uri=http://www.baidu.com"

S+ I: m w8 m# s4 y) g! o. v f

http://localhost:8080/oauth/token返回{ 8 B9 x3 x( K, D) n2 p9 B "access_token": "90a246fa-a9ee-4117-8401-ca9c869c5be9", / x% |& ^, B! d6 y! a

* g* Z* g0 k: Z" \4 I# X+ y' z

"token_type": "bearer", & Z8 t6 u6 B7 t2 [0 P# C2 k "refresh_token": "23503bc7-4494-4795-a047-98db75053374",% t4 ~6 o+ V) A5 U "expires_in"

2 ~. s2 I* m5 ~$ g5 E8 {+ n! O

: 3319,6 P1 I& P( t3 V4 \& S "scope": "app"" t, U! G- Y* c3 a7 h6 Y2 S, f }参考https://segmentfault.com/a/1190000012260914https://stackoverflow.com/questions/28537181/spring-security-oauth2-which-decides-security

/ P; k- m5 L" z0 u8 C1 `7 |

源码https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security-oauth2

; ^% I, Q/ A( i. A( X) J) b1 B" O: t, f9 f$ A# U% X6 i9 @+ a ( B2 I( z; F* L9 c; J / A( c1 O) G N& D1 T; O1 O( m! o! `% \* H( U4 R. E; s
回复

使用道具 举报

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

    本版积分规则

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

    GMT+8, 2026-7-9 00:44 , Processed in 0.045560 second(s), 25 queries , Gzip On.

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

    Powered by Discuz! X3.5

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