從零開始,手打一個(gè)權(quán)限管理系統(tǒng)(第十八章 集成OAuth2 Resource Server)

第十八章 集成OAuth2 Resource Server

前言

如果經(jīng)常有看Spring Security官方文檔的同學(xué)會(huì)發(fā)現(xiàn)船惨,我們寫的JWT這部分資源服務(wù)的鑒權(quán)操作其實(shí)是有官方實(shí)現(xiàn)的柜裸,那就是OAuth 2.0 Resource Server,本章節(jié)我們就來集成OAuth 2.0 Resource Server粱锐,來實(shí)現(xiàn)對(duì)資源服務(wù)的鑒權(quán)操作。


一扛邑、鑒定流程

官方文檔
我們需要了解Token的驗(yàn)證流程怜浅,具體如下:

2.png

  1. 當(dāng)用戶提交一個(gè)Token時(shí),BearerTokenAuthenticationFilter通過從HttpServletRequest中提取出來的Token來創(chuàng)建一個(gè)BearerTokenAuthenticationToken蔬崩。
  2. BearerTokenAuthenticationToken被傳遞到AuthenticationManager調(diào)用Authenticated方法進(jìn)行身份驗(yàn)證恶座。
  3. 如果失敗則調(diào)用失敗處理器
  4. 如果成功則可以繼續(xù)訪問資源接口

二、JWT身份驗(yàn)證的工作原理

官方文檔

1.png

1.將Token提交給ProviderManager沥阳。
2.ProviderManager會(huì)找到JwtAuthenticationProvider跨琳,并調(diào)用authenticate方法。
3.authenticate方法里面通過JwtDecoder來驗(yàn)證token桐罕,并轉(zhuǎn)換為Jwt對(duì)象脉让。
4.authenticate方法里面通過JwtAuthenticationConverter將Jwt對(duì)象轉(zhuǎn)換為已驗(yàn)證的Token對(duì)象。
5.驗(yàn)證成功就返回JwtAuthenticationToken對(duì)象功炮。


三溅潜、與JWT集成

原理說完了,現(xiàn)在該實(shí)操了薪伏,具體可以先抄官方的例子滚澜。
1.首先還是要引入依賴

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  </dependency>

2.添加公鑰和私鑰


img1d9bea856bc145c6ad4a081cd036da4b.png

配置文件里面新增如下配置:

jwt:
  private.key: classpath:app.key
  public.key: classpath:app.pub

3.官方參考代碼

/**
 * Security configuration for the main application.
 *
 * @author Josh Cummings
 */
@Configuration
public class RestConfig {

    @Value("${jwt.public.key}")
    RSAPublicKey key;

    @Value("${jwt.private.key}")
    RSAPrivateKey priv;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().authenticated()
                )
                .csrf((csrf) -> csrf.ignoringRequestMatchers("/token"))
                .httpBasic(Customizer.withDefaults())
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                        .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                );
        // @formatter:on
        return http.build();
    }

    @Bean
    UserDetailsService users() {
        // @formatter:off
        return new InMemoryUserDetailsManager(
            User.withUsername("user")
                .password("{noop}password")
                .authorities("app")
                .build()
        );
        // @formatter:on
    }

    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }

    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }

}

4.自己修改后的代碼

@Configuration
public class SecurityEncodeConfig {
    @Value("${jwt.public.key}")
    RSAPublicKey key;

    @Value("${jwt.private.key}")
    RSAPrivateKey priv;


    /**
     * jwt的解密方式
     */
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }

    /**
     * jwt的加密方式
     */
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }
}
@EnableWebSecurity
@EnableConfigurationProperties({PermitUrlProperties.class})
public class WebSecurityConfigurer {


    private final JwtEncoder jwtEncoder;
    private PermitUrlProperties permitUrlProperties;
    private List<AntPathRequestMatcher> antPathRequestMatcherList;

    public WebSecurityConfigurer(JwtEncoder jwtEncoder, PermitUrlProperties permitUrlProperties) {
        this.jwtEncoder = jwtEncoder;
        this.permitUrlProperties = permitUrlProperties;
    }

    @PostConstruct
    public void antPathMatcherList() {
        this.antPathRequestMatcherList = Optional.ofNullable(permitUrlProperties.getUrls()).orElse(new ArrayList<>()).stream().map(AntPathRequestMatcher::new).collect(Collectors.toList());
    }

    private final RequestMatcher PERMIT_ALL_REQUEST = (req) ->
            // 并且不包含token信息
            req.getHeader(HttpHeaders.AUTHORIZATION) == null &&
                    // 符合任意規(guī)則
                    this.antPathRequestMatcherList.stream().anyMatch(m -> m.matches(req));

    /**
     * 資源服務(wù)權(quán)限配置
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return
                http
                        .authorizeRequests()
                        // 白名單不攔截
                        .requestMatchers(PERMIT_ALL_REQUEST)
                        .permitAll()
                        .anyRequest().authenticated()
                        .and()
                        .cors(cors -> cors.disable())
                        .csrf(csrf -> csrf.disable())
                        .formLogin()
                        .successHandler(new JwtAuthenticationSuccessHandler(jwtEncoder))
                        .failureHandler(new JwtAuthenticationFailureHandler())
                        .and()
                        .logout(logout -> logout.logoutSuccessHandler(new JwtLogoutSuccessHandler()))
                        .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
                                .jwt()
                                .jwtAuthenticationConverter(c -> {
                                    JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
                                    jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES);
                                    jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
                                    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
                                    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
                                    return jwtAuthenticationConverter.convert(c);
                                })
                        )
                        //禁用session,JWT校驗(yàn)不需要session
                        .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                        .exceptionHandling(exception -> exception
                                //未授權(quán)異常
                                .accessDeniedHandler(new JwtAccessDeniedHandler())
                                .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                        ).build();


    }


    /**
     * 賬號(hào)密碼的加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder() {
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                /*
                 * 注意有提前加密
                 */
                return super.matches(decryptAES(rawPassword).trim(), encodedPassword);
            }
        };
    }

    private static String decryptAES(CharSequence password) {
        AES aes = new AES(Mode.CBC, Padding.NoPadding, new SecretKeySpec(SecurityConstants.FRONTEND_KEY.getBytes(), "AES"), new IvParameterSpec(SecurityConstants.FRONTEND_KEY.getBytes()));
        byte[] result = aes.decrypt(Base64.decode(password.toString().getBytes(StandardCharsets.UTF_8)));
        return new String(result, StandardCharsets.UTF_8);
    }
}

最核心的也就是這個(gè)WebSecurityConfigurer類里面的securityFilterChain配置嫁怀,這里面配置了白名單设捐、表單登錄、登錄成功和失敗后的處理方法塘淑、退出后的處理方法萝招,以及最重要的資源配置(oauth2ResourceServer),還有其他修改的地方請(qǐng)看源碼朴爬,我就不一一列舉了即寒。

當(dāng)前版本tag:2.0.3
代碼倉庫


四、 體驗(yàn)地址

后臺(tái)數(shù)據(jù)庫只給了部分權(quán)限,報(bào)錯(cuò)屬于正常母赵!
想學(xué)的老鐵給點(diǎn)點(diǎn)關(guān)注吧R菥簟!凹嘲!
歡迎留言交流J蟆!周蹭!

我是阿咕嚕趋艘,一個(gè)從互聯(lián)網(wǎng)慢慢上岸的程序員,如果喜歡我的文章凶朗,記得幫忙點(diǎn)個(gè)贊喲瓷胧,謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棚愤,一起剝皮案震驚了整個(gè)濱河市搓萧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宛畦,老刑警劉巖瘸洛,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異次和,居然都是意外死亡反肋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門踏施,熙熙樓的掌柜王于貴愁眉苦臉地迎上來石蔗,“玉大人,你說我怎么就攤上這事读规∽ザ剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵束亏,是天一觀的道長(zhǎng)铃在。 經(jīng)常有香客問我,道長(zhǎng)碍遍,這世上最難降的妖魔是什么定铜? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮怕敬,結(jié)果婚禮上揣炕,老公的妹妹穿的比我還像新娘。我一直安慰自己东跪,他們只是感情好畸陡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布鹰溜。 她就那樣靜靜地躺著,像睡著了一般丁恭。 火紅的嫁衣襯著肌膚如雪曹动。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天牲览,我揣著相機(jī)與錄音墓陈,去河邊找鬼。 笑死第献,一個(gè)胖子當(dāng)著我的面吹牛贡必,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庸毫,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼仔拟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了飒赃?” 一聲冷哼從身側(cè)響起理逊,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盒揉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兑徘,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刚盈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挂脑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藕漱。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖崭闲,靈堂內(nèi)的尸體忽然破棺而出肋联,到底是詐尸還是另有隱情,我是刑警寧澤刁俭,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布橄仍,位于F島的核電站,受9級(jí)特大地震影響牍戚,放射性物質(zhì)發(fā)生泄漏侮繁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一如孝、第九天 我趴在偏房一處隱蔽的房頂上張望宪哩。 院中可真熱鬧,春花似錦第晰、人聲如沸锁孟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽品抽。三九已至储笑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桑包,已是汗流浹背南蓬。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哑了,地道東北人赘方。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像弱左,于是被迫代替她去往敵國和親窄陡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容