SpringSecurity OAtu2+JWT實現微服務版本的單點登錄的示例

何為單點登錄

單點登錄通俗的話來講在微服務當中,在一個服務登錄後就能免去另一個服務的登錄操作,所謂單點登錄.

就好像你在微博總網站裡登錄後,然後在微博裡面的某一個模塊點進去後,就發現這個模塊竟然不用登錄瞭,不是因為這個模塊與主網站是一體的用一個SpringSecurity就可以搞定瞭,這裡面的水深著呢!
感興趣更深這個SpringSecurity建議去看看圖靈課堂的SpringSecurity,建議不要看尚矽谷的,那個版本說實話感覺有點老式瞭,教你手寫,其實我覺得,你既然學到瞭這個層次瞭,就應該能建立起快速打通一個新技術的能力,這個"打通"的意思不是要你深入,而是會用!別想著深入,因為沒有什麼實際意義,現在不是我們小白應該做的事,我們小白隻要打好基礎就可以瞭,比如java,jvm,spring,mysql這些!
沒有SpringSecurity基礎就別看這篇文章瞭,你可能看得懂,但是你肯定實現不出來,不信我你就看

認證中心

新建一個微服務模塊,可以另外建項目,也可以直接在你微服務項目裡建立模塊就可以瞭.

maven配置

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

用戶登錄邏輯

@Component
public class SheepUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        if( !"admin".equals(s) )
            throw new UsernameNotFoundException("用戶" + s + "不存在" );

        return new User( s, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_NORMAL,ROLE_MEDIUM"));
    }
}

OAtuh2配置

配置服務中心

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    SheepUserDetailsService sheepUserDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        // 定義瞭兩個客戶端應用的通行證
        clients.inMemory()
                .withClient("admin")
                .secret(new BCryptPasswordEncoder().encode("123456"))
                .authorizedGrantTypes("authorization_code", "refresh_token","password")
                .scopes("all")
                .autoApprove(true)
                .redirectUris("http://192.168.216.1:8001/login","http://192.168.216.1:8004/login");

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
        DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 一天有效期
        endpoints.tokenServices(tokenServices);

        //密碼模式配置
        endpoints.authenticationManager(authenticationManager).userDetailsService(sheepUserDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("testKey");
        return converter;
    }

}

配置規則中心

Configuration

public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {


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

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;


    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.setHideUserNotFoundExceptions(false);
        return authenticationProvider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**","/uac/oauth/token","/remove")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .logoutSuccessHandler(customLogoutSuccessHandler)
                // 無效會話
                .invalidateHttpSession(true)
                // 清除身份驗證
                .clearAuthentication(true)
                .permitAll();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

}
@Component
public class CustomLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 將子系統的cookie刪掉
        //建議將token也刪除,直接寫個controller接口就可以瞭,可以在前端調用/logout的同時調用刪除token接口
        Cookie[] cookies = request.getCookies();
        if(cookies != null && cookies.length>0){
            for (Cookie cookie : cookies){
                cookie.setMaxAge(0);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
        }
        super.handle(request, response, authentication);
    }
}

請求模塊

下面這個是請求模塊,也就是獨立出一個微服務,假如這個微服務是做業務的,會給認證中心發出請求,然後去熱證,這個模塊也算登錄瞭.
那麼有道友會問:其他模塊在該模塊登錄後還要登錄嗎?答案:不用!
至於要以什麼為基礎,接著往下看:

請求模塊這個依賴很關鍵

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

這個yml也很重要

auth-server: http://localhost:8085/uac
security:
  oauth2:
    client:
      client-id: admin
      client-secret: 123456
      user-authorization-uri: ${auth-server}/oauth/authorize #認證
      access-token-uri: ${auth-server}/oauth/token #獲取token
    resource:
      jwt:
        key-uri: ${auth-server}/oauth/token_key #忘瞭,反正要寫上

上面的認證和獲取token這兩個配置很重要,還有一個/oauth/check_token,這個是用來檢查token是否合法的,這些都怎麼用,是什麼,後面會說

下面這個也很重要

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableOAuth2Sso
public class ClientWebsecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
    //下面表示該模塊所有請求都要攔截
    //因為在yml配置瞭認證中心的各個路徑,所以會自動跳轉到認證中心去認證
    //如果你想在當前模塊排除哪些攔截,可以在下面去排除
        http.antMatcher("/**").authorizeRequests()
                .anyRequest().authenticated();
    }
}

真實請求

下面就可以正式去請求瞭,這裡很多網友都會有疑問,就是前面我在認證中心授予瞭權限之後,在其他模塊該如何去權限的規定呢?其實很簡單,有很多博主都沒有說,直接加上註解就可以瞭.

    @GetMapping("/get")
    @PreAuthorize("hasAuthority('ROLE_NORMAL')")
    public String get(HttpServletRequest request){
        System.out.println("函數進來瞭");
        return "uusb1j";
    }
}

這個時候,如果該微服務的get請求過來,就會跳轉到認證中心去認證.

一些小問題

這個時候隻要有相同配置的模塊都不用自行登錄瞭.如果有一個模塊進行登出請求,所有服務都會進行登出.

註意認證中心有項配置redirectUris(“http://192.168.216.1:8001/login”,“http://192.168.216.1:8004/login”),這個配置代表認證成功後重定向去哪個地址,但並不會真的重定向去對應模塊的login頁,而是重定向去你請求前被攔截的地址,但是這個有講究就是必須配置請求前所在模塊的地址,每個模塊對應一個重定向地址,最好是重定向到對應模塊的/login頁,其他頁也行.如果你從網關過來,然後你這裡重定向回網關是不行的,除非你網關有相關操作,不然你網關隻是一個轉發功能,是先轉發到對應模塊,然後發現該模塊的某個地址不可訪問,才去認證中心請求權限的,所以這裡的重定向地址還是具體到對應模塊才對,其他不行,有多個用","隔開就行.

到此這篇關於SpringSecurity OAtu2+JWT實現微服務版本的單點登錄的示例的文章就介紹到這瞭,更多相關SpringSecurity OAtu2 JWT單點登錄內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: