Springboot2.0 + OAuth2.0之密碼模式之單項目集成


概述:Spring OAuth2.0 是Spring Security中的模塊,提供項目安全認證(包括身份,權限,角色認證等)。其作用和 shiro 差不多,同屬於安全框架。但在使用角度個人覺得 shiro更易理解更易上手,更多差別還需了解。

其中OAuth2為我們提供了四種授權方式:

1、授權碼模式(authorization code)
2、簡化模式(implicit)
3、密碼模式(resource owner password credentials)
4、客戶端模式(client credentials)

而較常用的則為密碼模式授權碼模式,而授權碼模式又是最為安全的模式,復雜程度成正比。完整的項目結構分為:客戶端服務,認證服務,資源服務。客戶端需要訪問資源服務的資源時,則需要得到認證服務的認證。

一、密碼模式

(1)單項目集成(即認證服務即為資源服務)其項目結構如下:

此項目屬於Maven項目。

•  配置認證服務器

首先需要配置認證服務所必須的配置,為 AuthorizationServerConfig 和 WebSecurityConfig 兩個配置文件,其詳情如下:

package com.liuzj.oauth2server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;

/**
 * 這個注解告訴 Spring 這個應用是 OAuth2 的授權服務器,
 * 提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error
 *
 * @author liuzj
 * @date  2019-01-15
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * webSecurityConfig 中配置的AuthenticationManager
     */
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    /**
     * 此項目使用數據庫保存 token 等信息所以要配置數據源
     */
    @Autowired
    private DataSource dataSource;

    /**
     * webSecurityConfig 中配置的 userDetailsService
     */
    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    /**
     * webSecurityConfig 中配置的 passwordEncoder(使用MD5加密)
     */
    @Autowired
    PasswordEncoder passwordEncoder;

    @Bean
    public TokenStore tokenStore() {

        //使用內存中的 token store
//        return new InMemoryTokenStore();

        //使用Jdbctoken store
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 對 oauth_client_details 表的一些操作
     *
     * @return ClientDetailsService
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Autowired
    TokenStore tokenStore;

    @Autowired
    ClientDetailsService clientDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource); 
          // 請求token的時候會將client_id,client_secret等信息保存到 oauth_client_details 表中,所以需要手動創建該表
           // 注意:以下注釋的代碼在請求了一次 token 之后則可以注釋掉,否則如果不換 client 名字的話會因為主鍵沖突無法插入 client 信息。也可以一開始就注釋,手動添加記錄到數據庫
// .withClient("client") // .secret(passwordEncoder.encode("123456")) // .authorizedGrantTypes("authorization_code", "refresh_token", // "password", "implicit") // 四種認證模式 // .scopes("all") // .authorities("ROLE_admin","ROLE_user") // .redirectUris("http://www.baidu.com") // .accessTokenValiditySeconds(120000) // .refreshTokenValiditySeconds(50000); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()") //允許check_token訪問 .checkTokenAccess("permitAll()") //允許表單登錄 .allowFormAuthenticationForClients(); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore()); endpoints.userDetailsService(userDetailsService); endpoints.setClientDetailsService(clientDetailsService); //配置TokenServices參數 DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(true); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); // access_token 過期時間:5s tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // refresh_token 過期時間,默認不過期 // tokenServices.setReuseRefreshToken(true); // tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.SECONDS.toSeconds(20)); endpoints.tokenServices(tokenServices); } @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setSupportRefreshToken(true); tokenServices.setTokenStore(tokenStore); return tokenServices; } }
package com.liuzj.oauth2server.config;

import com.liuzj.oauth2server.utils.MD5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 安全配置
 *
 * @author liuzj
 * @date  2019-01-15
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * userDetailsService 獲取token的時候對用戶進行一些自定義過濾,並將保存用戶信息(用戶名,密碼,角色等)
     */
    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    /**
     * 使用MD5對client_secreat進行加密,可以使用默認的加密方式也可以自定義,這里使用MD5加密方式
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return MD5Util.encodeMD5(String.valueOf(charSequence));
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(MD5Util.encodeMD5(String.valueOf(charSequence)));
            }
        };
    }

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

    /**
     * 配置用戶簽名服務 主要是user-details 機制,
     *
     * @param auth 簽名管理器構造器,用於構建用戶具體權限控制
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http
                .requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated()
                .and()
                .formLogin().permitAll(); //新增login form支持用戶登錄及授權
    }
}

以上有一個 UserDetailsService 這個接口是提供出來我們自己實現的,在實現代碼中可以自定義一下過濾規則,比如判斷用戶的合法性,具體如下:

package com.liuzj.oauth2server.config.selfauthor;

import com.liuzj.oauth2server.domain.User;
import com.liuzj.oauth2server.repositories.UserRepository;
import com.liuzj.oauth2server.utils.MD5Util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * 進行登錄用戶自定義過濾
 *
 * @author liuzj
 * @date 2019-01-15
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {



    protected final Log logger = LogFactory.getLog(getClass());

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(final String username)
            throws UsernameNotFoundException {

        User user = userRepository.findByUserName(username);

        logger.info("loadUserByUsername username=" + username);
     // 如果用戶不存在則認證失敗
        if(user == null){
            throw new UsernameNotFoundException(username + " not found");
        }

        // 注意:此處的密碼記得要進行加密,因為在前面配置的時候是使用了 MD5 加密,所以這里也要進行加密
        return new UserInfo(username, MD5Util.encodeMD5(user.getPassword()),user.getRole());
    }

}

以上只是將主要的配置附上,還有 user 表以及 集成 mybatis 等步驟沒有寫出,請自行倒騰。。。注意:user 對象需要實現 Serializable 接口(可序列化),因為在認證的時候該對象是需要進行IO操作的。

配置好認證服務之后跑如下 SQL 創建幾張必須的表(存儲 token 以及 client 等信息),因為本案例是使用數據庫存儲 token 的,當然也可以存在內存以及通過 jwt 方式:

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL,
  `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ---------------------------- -- Table structure for user -- ----------------------------
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '名稱',
  `password` varchar(255) NOT NULL DEFAULT '0' COMMENT '密碼',
  `role` varchar(255) NOT NULL COMMENT '角色',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

到此,啟動項目,訪問連接:http://localhost:8001/oauth/token?username=liuzj&password=123&grant_type=password&client_id=client&client_secret=123456 (POST)

 正常情況下如下返回:

這樣就成功通過 賬號和密碼獲取了 token。還可以嘗試如下操作:

檢查 token :http://localhost:8001/oauth/check_token?token=f0cb83c3-6dd7-4c63-ab9f-2bdf3d492b46 (get)

刷新 token: http://localhost:8001/oauth/token?grant_type=refresh_token&refresh_token=cd08de58-b91f-45c6-b165-6b8bd4b7bfdc&client_id=client&client_secret=123456 (post)


 • 配置資源服務器

首先配置資源服務器專有配置,如下:

package com.liuzj.oauth2server.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * 這個類表明了此應用是OAuth2 的資源服務器,此處主要指定受資源服務器保護的資源鏈接
 * 默認情況下spring security oauth2的http配置會被WebSecurityConfigurerAdapter的配置覆蓋
 *
 * @author liuzj
 * @date 2019-01-15
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()//禁用了csrf(跨站請求偽造)功能
                .authorizeRequests()//限定簽名成功的請求
                //必須認證過后才可以訪問;注意:hasAnyRole 會默認加上ROLE_前綴,而hasAuthority不會加前綴
                .antMatchers("/decision/**","/govern/**").hasAnyRole("user") // 在角色過濾的時候需要注意user角色需要加角色前綴
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/test/**").authenticated()
                // 免驗證請求
                .antMatchers("/oauth/**").permitAll();
    }

}

 

配置好如上配置就搞定了。重啟項目測試訪問項目接口(接口自行去碼):

如果直接訪問它便會提示:此資源訪問需要認證。所以得先獲取 token(上面有講)帶上 token 即可訪問接口,如下:

 

 

 到此密碼認證告一段落。


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM