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
|