spring cloud oauth2 搭建認證中心(授權(quán)服務(wù)器)

單點登錄

什么是單點登錄孙蒙?單點登錄全稱Single Sign On(以下簡稱SSO),是指在多系統(tǒng)應(yīng)用群中登錄一個系統(tǒng)悲雳,便可在其他所有系統(tǒng)中得到授權(quán)而無需再次登錄挎峦,包括單點登錄與單點注銷兩部分

相比于單系統(tǒng)登錄,sso需要一個獨立的認證中心合瓢,只有認證中心能接受用戶的用戶名密碼等安全信息坦胶,其他系統(tǒng)不提供登錄入口,只接受認證中心的間接授權(quán)歪玲。間接授權(quán)通過令牌實現(xiàn)迁央,sso認證中心驗證用戶的用戶名密碼沒問題掷匠,創(chuàng)建授權(quán)令牌滥崩,在接下來的跳轉(zhuǎn)過程中,授權(quán)令牌作為參數(shù)發(fā)送給各個子系統(tǒng)讹语,子系統(tǒng)拿到令牌钙皮,即得到了授權(quán),可以借此創(chuàng)建局部會話顽决,局部會話登錄方式與單系統(tǒng)的登錄方式相同短条。這個過程,也就是單點登錄的原理才菠。

oauth2

在oauth中我們通常將他分為認證中心和資源中心茸时。二者可以放在一起,但是對于微服務(wù)來說赋访,每個獨自的微服務(wù)可能就是一個資源中心可都。
在oauth2中有幾種授權(quán)模式:
1缓待、授權(quán)碼模式
2、密碼模式
3渠牲、客戶端模式
4旋炒、簡易模式
具體請求流程不是本文中點,我們著重講解Spring cloud oauth2搭建認證中心签杈。

在oauth中我們通常將他分為認證中心和資源中心瘫镇。
對于我們的微服務(wù)來說,每個獨立的微服務(wù)即是一個個資源中心答姥,用戶想要請求微服務(wù)的數(shù)據(jù)铣除,需要攜帶認證中心頒發(fā)的tocken。

1.引入相關(guān)pom依賴

這里我們使用1.5.2.RELEASE的spring boot版本鹦付,因為我們會將oauth2交給cloud管理通孽,所以我們同時引入了cloud的依賴,cloud版本使用SR5

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.2.RELEASE</version>
    <relativePath></relativePath>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- 熱部署 -->
<!-- <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency> -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
        <optional>true</optional>
</dependency>
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

2.編寫spring boot啟動類

@SpringBootApplication
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {
        "com.dahaonetwork.smartfactory.authserver.mapper"
        })
public class AuthenticationApplication {
    /** 主類  */
    public static void main(String[] args) {
        SpringApplication.run(AuthenticationApplication.class, args);
    }
}

3.聲明一個授權(quán)服務(wù)器

聲明一個授權(quán)服務(wù)器只需要繼承 AuthorizationServerConfigurerAdapter睁壁,添加
@EnableAuthorizationServer 注解背苦。
@EnableAuthorizationServer 這個注解告訴 Spring 這個應(yīng)用是 OAuth2 的認證中心。
并且復(fù)寫如下三個方法:
ClientDetailsServiceConfigurer:這個configurer定義了客戶端細節(jié)服務(wù)潘明⌒屑粒客戶詳細信息可以被初始化。
AuthorizationServerSecurityConfigurer:在令牌端點上定義了安全約束钳降。
AuthorizationServerEndpointsConfigurer:定義了授權(quán)和令牌端點和令牌服務(wù)厚宰。

配置客戶端詳細步驟
ClientDetailsServiceConfigurer 類(AuthorizationServerConfigurer類中的一個調(diào)用類)可以用來定義一個基于內(nèi)存的或者JDBC的客戶端信息服務(wù)。
客戶端對象重要的屬性有:
clientId:(必須)客戶端id遂填。
secret:(對于可信任的客戶端是必須的)客戶端的私密信息铲觉。
scope:客戶端的作用域。如果scope未定義或者為空(默認值)吓坚,則客戶端作用域不受限制撵幽。
authorizedGrantTypes:授權(quán)給客戶端使用的權(quán)限類型。默認值為空礁击。
authorities:授權(quán)給客戶端的權(quán)限(Spring普通的安全權(quán)限)盐杂。
在運行的應(yīng)用中,可以通過直接訪問隱藏的存儲文件(如:JdbcClientDetailsService中用到的數(shù)據(jù)庫表)或者通過實現(xiàn)ClientDetailsManager 接口(也可以實現(xiàn)ClientDetailsService 接口哆窿,或者實現(xiàn)兩個接口)來更新客戶端信息链烈。

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{...}
@Override 
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {...}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {...}

具體代碼如下所示:這里我們將tocken信息存儲在mysql中,分布式下你可以存儲在redis中挚躯,全局共享强衡。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisConnectionFactory connectionFactory;
    @Autowired
    private DataSource dataSource;
    /**
     * @Title: tokenStore
     * @Description: 用戶驗證信息的保存策略,可以存儲在內(nèi)存中码荔,關(guān)系型數(shù)據(jù)庫中漩勤,redis中
     * @param 
     * @return TokenStore
     * @throws
     */
    @Bean
    public TokenStore tokenStore(){
        //return new RedisTokenStore(connectionFactory);
        //return new InMemoryTokenStore();
        return new JdbcTokenStore(dataSource);
    }
    
    @Bean // 聲明 ClientDetails實現(xiàn)
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
    
    /**
     * 
     * 這個方法主要是用于校驗注冊的第三方客戶端的信息号涯,可以存儲在數(shù)據(jù)庫中,默認方式是存儲在內(nèi)存中锯七,如下所示链快,注釋掉的代碼即為內(nèi)存中存儲的方式
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
        //       clients.inMemory()
        //       .withClient("client").secret("123456").scopes("read")
        //       .authorizedGrantTypes("authorization_code", "password", "refresh_token")
        //       .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT");
        //       //.authorizedGrantTypes("password","authorization_code","client_credentials","refresh_token");
        //       //.authorizedGrantTypes("password","refresh_token");
        //       //.redirectUris("https://www.getpostman.com/oauth2/callback");
        //   /*redirectUris 關(guān)于這個配置項,是在 OAuth2協(xié)議中眉尸,認證成功后的回調(diào)地址域蜗,此值同樣可以配置多個*/
        
        
        clients.withClientDetails(clientDetails());
        clients.jdbc(dataSource);
    }
    
   
    /**
     * 這個方法主要的作用用于控制token的端點等信息
     */
    @Override 
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore());
        //endpoints.userDetailsService(userService);
        // 配置TokenServices參數(shù) 可以考慮使用[DefaultTokenServices],它使用隨機值創(chuàng)建令牌
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }
    
    /**
     允許表單驗證噪猾,瀏覽器直接發(fā)送post請求即可獲取tocken
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
                "isAuthenticated()");
        oauthServer.allowFormAuthenticationForClients();
    }
}

4開啟Spring Security的功能

spring security用來驗證用戶賬號密碼霉祸,對請求路勁做處理等。繼承WebSecurityConfigurerAdapter 使用@EnableWebMvcSecurity 注解開啟Spring Security的功能袱蜡。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Autowired
    private MyAuthenticationProvider provider;
    
    @Autowired
    private UserDetailsService userService;
    /**
     * 用戶認證
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(provider);
        auth.userDetailsService(userService);   
//      auth.inMemoryAuthentication()
//            .withUser("user").password("123456").authorities("ROLE_USER");
    }
    
    /**
     * 1:
     * 請求授權(quán):
     * spring security 使用以下匹配器來匹配請求路勁:
     *      antMatchers:使用ant風格的路勁匹配
     *      regexMatchers:使用正則表達式匹配路勁
     * anyRequest:匹配所有請求路勁
     * 在匹配了請求路勁后丝蹭,需要針對當前用戶的信息對請求路勁進行安全處理。
     * 2:定制登錄行為坪蚁。
     *      formLogin()方法定制登錄操作
     *      loginPage()方法定制登錄頁面訪問地址
     *      defaultSuccessUrl()登錄成功后轉(zhuǎn)向的頁面
     *      permitAll()
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
                http
                .authorizeRequests()
                .antMatchers(
                        StaticParams.PATHREGX.API, 
                        StaticParams.PATHREGX.CSS,
                        StaticParams.PATHREGX.JS,
                        StaticParams.PATHREGX.IMG).permitAll()//允許用戶任意訪問
                        .anyRequest().authenticated()//其余所有請求都需要認證后才可訪問
                .and()
                .formLogin()
                    //.loginPage("/login/login.do")  /
                               //.defaultSuccessUrl("/hello2")
                        .permitAll();//允許用戶任意訪問
                http.csrf().disable();
    }
    
    /**
     * 密碼模式下必須注入的bean authenticationManagerBean
     * 認證是由 AuthenticationManager 來管理的奔穿,
     * 但是真正進行認證的是 AuthenticationManager 中定義的AuthenticationProvider。
     *  AuthenticationManager 中可以定義有多個 AuthenticationProvider
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

5自定義用戶認證實現(xiàn)

認證是由AuthenticationManager 來管理的敏晤,
但是真正進行認證的是 AuthenticationManager 中定義的AuthenticationProvider贱田。
AuthenticationManager 中可以定義有多個 AuthenticationProvider,
我們在四步驟中AuthenticationManagerBuilder中添加了當前的Provider 嘴脾。

@Named
@Component
public class MyAuthenticationProvider extends DaoAuthenticationProvider  {


    /** 規(guī)則校驗 */
    @Resource(name = "passwordService")
    private PasswordService passwordService;

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    // 構(gòu)造函數(shù)中注入
    @Inject
    public MyAuthenticationProvider(UserDetailsService userDetailsService)
    {
        this.setUserDetailsService(userDetailsService);
    }

    /**
     * 自定義驗證方式
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        
        MyUserDetails userDetails = (MyUserDetails) 
                this.getUserDetailsService().loadUserByUsername(username);

        //按登錄規(guī)則校驗用戶
        passwordService.validateRules(userDetails.getUser(), password);

        Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
        Authentication authenticationToken = new UsernamePasswordAuthenticationToken(JSON.toJSONString(userDetails,SerializerFeature.WriteMapNullValue), password, authorities); 
        return authenticationToken;
        
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

}

6自定義UserDetailsService

自定義需要實現(xiàn)UserDetailsService接口男摧,并且重寫loadUserByUsername方法。返回的用戶信息需要實現(xiàn)UserDatails接口译打。

@Service("userInfo")
public class UserInfoService implements UserDetailsService{
    
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // TODO Auto-generated method stub
        Map<String, String> paramMap=new HashMap<String, String>();
        paramMap.put("loginId", username);
        User user=userMapper.getUserByloginIds(paramMap);
        
        if(user ==null) {
            throw new BadCredentialsException(Constants.getReturnStr(Constants.USER_NOT_FOUND, Constants.USER_NOT_FOUND_TIPS));
        }
        MyUserDetails userDetails = new MyUserDetails();
        userDetails.setUserName(username);
        userDetails.setPassword(user.getPassword());
        userDetails.setUser(user);
        return userDetails;
    }

}
public class MyUserDetails implements UserDetails{
    
    private static final long seriaVersionUID=1L;
    
    private String userName;
    
    private String password;
    
    private User user;
    
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setPassword(String password) {
        this.password = password;
    }
     
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
    
    /**
     * 重寫getAuthorities方法耗拓,將用戶的角色作為權(quán)限
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //TODO 后續(xù)帶完善
        return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_SUPER");
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

7相關(guān)數(shù)據(jù)庫表結(jié)構(gòu)

為了文章不過于冗余,表結(jié)構(gòu)和代碼將在將在gitlab上共享
https://gitlab.com/liguobao666/cloud-service

8相關(guān)配置文件

spring.application.name=oauth-server
server.port=8043
server.context-path=/uaa

logging.level.org.springframework.security=DEBUG
security.oauth2.resource.serviceId= ${PREFIX:}resource
security.oauth2.resource.filter-order=3

database.url=jdbc:mysql://192.168.7.175:3306/oauth2
spring.datasource.url=${database.url}?useUnicode\=true&characterEncoding\=UTF-8&useOldAliasMetadataBehavior\=true
spring.datasource.username=devdb
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver


logging.level.root=info
logging.level.com.dahaonetwork.smartfactory.authserver=debug
logging.level.org.springframework.security=info
#mybatis show sql
logging.level.org.springframework=WARN
spring.thymeleaf.cache=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

mybatis.mapper-locations=classpath:mapper/*.xml

9測試

這里我們使用密碼模式來獲取tocken:



這樣認證服務(wù)器就搭建完成了奏司,瀏覽器只需要攜帶tocken就可以訪問資源服務(wù)器上的相關(guān)信息乔询。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市结澄,隨后出現(xiàn)的幾起案子哥谷,更是在濱河造成了極大的恐慌,老刑警劉巖麻献,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異猜扮,居然都是意外死亡勉吻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門旅赢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來齿桃,“玉大人惑惶,你說我怎么就攤上這事《套荩” “怎么了带污?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長香到。 經(jīng)常有香客問我鱼冀,道長,這世上最難降的妖魔是什么悠就? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任千绪,我火速辦了婚禮,結(jié)果婚禮上梗脾,老公的妹妹穿的比我還像新娘荸型。我一直安慰自己,他們只是感情好炸茧,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布瑞妇。 她就那樣靜靜地躺著,像睡著了一般梭冠。 火紅的嫁衣襯著肌膚如雪踪宠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天妈嘹,我揣著相機與錄音柳琢,去河邊找鬼。 笑死润脸,一個胖子當著我的面吹牛柬脸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毙驯,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼倒堕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了爆价?” 一聲冷哼從身側(cè)響起垦巴,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铭段,沒想到半個月后骤宣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡序愚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年憔披,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡芬膝,死狀恐怖望门,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锰霜,我是刑警寧澤筹误,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站癣缅,受9級特大地震影響厨剪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜所灸,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一丽惶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爬立,春花似錦钾唬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吟策,卻和暖如春儒士,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檩坚。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工着撩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匾委。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓拖叙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赂乐。 傳聞我的和親對象是個殘疾皇子薯鳍,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355