溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring Boot Security 整合 OAuth2 設(shè)計(jì)安全API接口服務(wù)

發(fā)布時(shí)間:2020-06-14 10:44:09 來源:網(wǎng)絡(luò) 閱讀:4294 作者:程序員果果 欄目:編程語(yǔ)言

簡(jiǎn)介

OAuth是一個(gè)關(guān)于授權(quán)(authorization)的開放網(wǎng)絡(luò)標(biāo)準(zhǔn),在全世界得到廣泛應(yīng)用,目前的版本是2.0版。本文重點(diǎn)講解Spring Boot項(xiàng)目對(duì)OAuth3進(jìn)行的實(shí)現(xiàn),如果你對(duì)OAuth3不是很了解,你可以先理解 OAuth 2.0 - 阮一峰,這是一篇對(duì)于oauth3很好的科普文章。

OAuth3概述

oauth3根據(jù)使用場(chǎng)景不同,分成了4種模式

  • 授權(quán)碼模式(authorization code)
  • 簡(jiǎn)化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

在項(xiàng)目中我們通常使用授權(quán)碼模式,也是四種模式中最復(fù)雜的,通常網(wǎng)站中經(jīng)常出現(xiàn)的微博,qq第三方登錄,都會(huì)采用這個(gè)形式。

Oauth3授權(quán)主要由兩部分組成:

  • Authorization server:認(rèn)證服務(wù)
  • Resource server:資源服務(wù)

在實(shí)際項(xiàng)目中以上兩個(gè)服務(wù)可以在一個(gè)服務(wù)器上,也可以分開部署。下面結(jié)合spring boot來說明如何使用。

快速上手

之前的文章已經(jīng)對(duì) Spring Security 進(jìn)行了講解,這一節(jié)對(duì)涉及到 Spring Security 的配置不詳細(xì)講解。若不了解 Spring Security 先移步到 Spring Boot Security 詳解。

建表

客戶端信息可以存儲(chǔ)在內(nèi)存、redis和數(shù)據(jù)庫(kù)。在實(shí)際項(xiàng)目中通常使用redis和數(shù)據(jù)庫(kù)存儲(chǔ)。本文采用數(shù)據(jù)庫(kù)。Spring 0Auth3 己經(jīng)設(shè)計(jì)好了數(shù)據(jù)庫(kù)的表,且不可變。表及字段說明參照:Oauth3數(shù)據(jù)庫(kù)表說明 。

創(chuàng)建0Auth3數(shù)據(jù)庫(kù)的腳本如下:

DROP TABLE IF EXISTS `clientdetails`;
DROP TABLE IF EXISTS `oauth_access_token`;
DROP TABLE IF EXISTS `oauth_approvals`;
DROP TABLE IF EXISTS `oauth_client_details`;
DROP TABLE IF EXISTS `oauth_client_token`;
DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

為了測(cè)試方便,我們先插入一條客戶端信息。

INSERT INTO `oauth_client_details` VALUES ('dev', '', 'dev', 'app', 'password,client_credentials,authorization_code,refresh_token', 'http://www.baidu.com', '', 3600, 3600, '{\"country\":\"CN\",\"country_code\":\"086\"}', 'false');

用戶、權(quán)限、角色用到的表如下:

DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;

CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`) 
);

INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/**','',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/**','',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);

項(xiàng)目結(jié)構(gòu)

resources
|____templates
| |____login.html
| |____application.yml
java
|____com
| |____gf
| | |____SpringbootSecurityApplication.java
| | |____config
| | | |____SecurityConfig.java
| | | |____MyFilterSecurityInterceptor.java
| | | |____MyInvocationSecurityMetadataSourceService.java
| | | |____ResourceServerConfig.java
| | | |____WebResponseExceptionTranslateConfig.java
| | | |____AuthorizationServerConfiguration.java
| | | |____MyAccessDecisionManager.java
| | |____entity
| | | |____User.java
| | | |____RolePermisson.java
| | | |____Role.java
| | |____mapper
| | | |____PermissionMapper.java
| | | |____UserMapper.java
| | | |____RoleMapper.java
| | |____controller
| | | |____HelloController.java
| | | |____MainController.java
| | |____service
| | | |____MyUserDetailsService.java

關(guān)鍵代碼

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth3-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth3-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth3-autoconfigure</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>
SecurityConfig

支持password模式要配置AuthenticationManager

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //校驗(yàn)用戶
        auth.userDetailsService( userService ).passwordEncoder( new PasswordEncoder() {
            //對(duì)密碼進(jìn)行加密
            @Override
            public String encode(CharSequence charSequence) {
                System.out.println(charSequence.toString());
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }
            //對(duì)密碼進(jìn)行判斷匹配
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                boolean res = s.equals( encode );
                return res;
            }
        } );

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.requestMatchers()
                .antMatchers("/oauth/**","/login","/login-error")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated()
                .and()
                .formLogin().loginPage( "/login" ).failureUrl( "/login-error" );
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return Objects.equals(charSequence.toString(),s);
            }
        };
    }

}

AuthorizationServerConfiguration 認(rèn)證服務(wù)器配置

/**
 * 認(rèn)證服務(wù)器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    /**
     * 注入權(quán)限驗(yàn)證控制器 來支持 password grant type
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 注入userDetailsService,開啟refresh_token需要用到
     */
    @Autowired
    private MyUserDetailsService userDetailsService;

    /**
     * 數(shù)據(jù)源
     */
    @Autowired
    private DataSource dataSource;

    /**
     * 設(shè)置保存token的方式,一共有五種,這里采用數(shù)據(jù)庫(kù)的方式
     */
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore( dataSource );
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        /**
         * 配置oauth3服務(wù)跨域
         */
        CorsConfigurationSource source = new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.addAllowedHeader("*");
                corsConfiguration.addAllowedOrigin(request.getHeader( HttpHeaders.ORIGIN));
                corsConfiguration.addAllowedMethod("*");
                corsConfiguration.setAllowCredentials(true);
                corsConfiguration.setMaxAge(3600L);
                return corsConfiguration;
            }
        };

        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients()
                .addTokenEndpointAuthenticationFilter(new CorsFilter(source));
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //開啟密碼授權(quán)類型
        endpoints.authenticationManager(authenticationManager);
        //配置token存儲(chǔ)方式
        endpoints.tokenStore(tokenStore);
        //自定義登錄或者鑒權(quán)失敗時(shí)的返回信息
        endpoints.exceptionTranslator(webResponseExceptionTranslator);
        //要使用refresh_token的話,需要額外配置userDetailsService
        endpoints.userDetailsService( userDetailsService );

    }

}

ResourceServerConfig 資源服務(wù)器配置

/**
 * 資源提供端的配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 這里設(shè)置需要token驗(yàn)證的url
     * 這些url可以在WebSecurityConfigurerAdapter中排除掉,
     * 對(duì)于相同的url,如果二者都配置了驗(yàn)證
     * 則優(yōu)先進(jìn)入ResourceServerConfigurerAdapter,進(jìn)行token驗(yàn)證。而不會(huì)進(jìn)行
     * WebSecurityConfigurerAdapter 的 basic auth或表單認(rèn)證。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/hi")
                .and()
                .authorizeRequests()
                .antMatchers("/hi").authenticated();
    }

}

關(guān)鍵代碼就是這些,其他類代碼參照后面提供的源碼地址。

驗(yàn)證

密碼授權(quán)模式

[ 密碼模式需要參數(shù):username , password , grant_type , client_id , client_secret ]

請(qǐng)求token

curl -X POST -d "username=admin&password=123456&grant_type=password&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token

返回

{
    "access_token": "d94ec0aa-47ee-4578-b4a0-8cf47f0e8639",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3475,
    "scope": "app"
}

不攜帶token訪問資源,

curl http://localhost:8080/hi\?name\=zhangsan

返回提示未授權(quán)

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

攜帶token訪問資源

curl http://localhost:8080/hi\?name\=zhangsan\&access_token\=164471f7-6fc6-4890-b5d2-eb43bda3328a

返回正確

hi , zhangsan

刷新token

curl  -X POST -d 'grant_type=refresh_token&refresh_token=23503bc7-4494-4795-a047-98db75053374&client_id=dev&client_secret=dev' http://localhost:8080/oauth/token

返回

{
    "access_token": "ef53eb01-eb9b-46d8-bd58-7a0f9f44e30b",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3599,
    "scope": "app"
}

客戶端授權(quán)模式

[ 客戶端模式需要參數(shù):grant_type , client_id , client_secret ]

請(qǐng)求token

curl -X POST -d "grant_type=client_credentials&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token

返回

{
    "access_token": "a7be47b3-9dc8-473e-967a-c7267682dc66",
    "token_type": "bearer",
    "expires_in": 3564,
    "scope": "app"
}

授權(quán)碼模式

獲取code

瀏覽器中訪問如下地址:

http://localhost:8080/oauth/authorize?response_type=code&client_id=dev&redirect_uri=http://www.baidu.com

跳轉(zhuǎn)到登錄頁(yè)面,輸入賬號(hào)和密碼進(jìn)行認(rèn)證:

Spring Boot Security 整合 OAuth2 設(shè)計(jì)安全API接口服務(wù)

認(rèn)證后會(huì)跳轉(zhuǎn)到授權(quán)確認(rèn)頁(yè)面(oauth_client_details 表中 “autoapprove” 字段設(shè)置為true 時(shí),不會(huì)出授權(quán)確認(rèn)頁(yè)面):

Spring Boot Security 整合 OAuth2 設(shè)計(jì)安全API接口服務(wù)

確認(rèn)后,會(huì)跳轉(zhuǎn)到百度,并且地址欄中會(huì)帶上我們想得到的code參數(shù):

Spring Boot Security 整合 OAuth2 設(shè)計(jì)安全API接口服務(wù)

通過code換token

curl -X POST -d "grant_type=authorization_code&code=qS03iu&client_id=dev&client_secret=dev&redirect_uri=http://www.baidu.com" http://localhost:8080/oauth/token

返回

{
    "access_token": "90a246fa-a9ee-4117-8401-ca9c869c5be9",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3319,
    "scope": "app"
}

參考

https://segmentfault.com/a/1190000012260914

https://stackoverflow.com/questions/28537181/spring-security-oauth3-which-decides-security

源碼

https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security-oauth3

歡迎關(guān)注我的公眾號(hào)《程序員果果》,關(guān)注有驚喜~~
Spring Boot Security 整合 OAuth2 設(shè)計(jì)安全API接口服務(wù)

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI