Spring Security源碼分析十二:Spring Security OAuth2基於JWT實現單點登陸

單點登陸(英語:Single sign-on,縮寫爲 SSO),又譯爲單一簽入,一種對於許多相互關連,可是又是各自獨立的軟件系統,提供訪問控制的屬性。當擁有這項屬性時,當用戶登陸時,就能夠獲取全部系統的訪問權限,不用對每一個單一系統都逐一登陸。這項功能一般是以輕型目錄訪問協議(LDAP)來實現,在服務器上會將用戶信息存儲到LDAP數據庫中。相同的,單一註銷(single sign-off)就是指,只須要單一的註銷動做,就能夠結束對於多個系統的訪問權限。css

Security OAuth2 單點登陸流程示意圖

https://user-gold-cdn.xitu.io/2018/1/25/1612df517b7d9d71?w=1546&h=1526&f=png&s=64834
https://user-gold-cdn.xitu.io/2018/1/25/1612df517b7d9d71?w=1546&h=1526&f=png&s=64834

  1. 訪問client1
  2. client1將請求導向sso-server
  3. 贊成受權
  4. 攜帶受權碼code返回client1
  5. client1拿着受權碼請求令牌
  6. 返回JWT令牌
  7. client1解析令牌並登陸
  8. client1訪問client2
  9. client2將請求導向sso-server
  10. 贊成受權
  11. 攜帶受權碼code返回client2
  12. client2拿着受權碼請求令牌
  13. 返回JWT令牌
  14. client2解析令牌並登陸

用戶的登陸狀態是由sso-server認證中心來保存的,登陸界面和帳號密碼的驗證也是sso-server認證中心來作的(client1clien2返回token是不一樣的,但解析出來的用戶信息是同一個用戶)。html

Security OAuth2 實現單點登陸

項目結構

https://user-gold-cdn.xitu.io/2018/1/25/1612df517b8f34ca?w=551&h=736&f=png&s=43350
https://user-gold-cdn.xitu.io/2018/1/25/1612df517b8f34ca?w=551&h=736&f=png&s=43350

sso-server

認證服務器

@Configuration
@EnableAuthorizationServer
public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /** * 客戶端一些配置 * @param clients * @throws Exception */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("merryyou1")
                .secret("merryyousecrect1")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("all")
                .and()
                .withClient("merryyou2")
                .secret("merryyousecrect2")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("all");
    }

    /** * 配置jwttokenStore * @param endpoints * @throws Exception */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
    }

    /** * springSecurity 受權表達式,訪問merryyou tokenkey時須要通過認證 * @param security * @throws Exception */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("isAuthenticated()");
    }

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

    /** * 生成JTW token * @return */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("merryyou");
        return converter;
    }
}
複製代碼

security配置

@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
                .and().authorizeRequests()
                .antMatchers("/authentication/require",
                        "/authentication/form",
                        "/**/*.js",
                        "/**/*.css",
                        "/**/*.jpg",
                        "/**/*.png",
                        "/**/*.woff2"
                )
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
// http.formLogin().and().authorizeRequests().anyRequest().authenticated();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}
複製代碼

SsoUserDetailsService

@Component
public class SsoUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
    }
}
複製代碼

application.yml

server:
 port: 8082
 context-path: /uaa
spring:
 freemarker:
 allow-request-override: false
 allow-session-override: false
 cache: true
 charset: UTF-8
 check-template-location: true
 content-type: text/html
 enabled: true
 expose-request-attributes: false
 expose-session-attributes: false
 expose-spring-macro-helpers: true
 prefer-file-system-access: true
 suffix: .ftl
 template-loader-path: classpath:/templates/
複製代碼

sso-client1

SsoClient1Application

@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient1Application {

    @GetMapping("/user")
    public Authentication user(Authentication user) {
        return user;
    }

    public static void main(String[] args) {
        SpringApplication.run(SsoClient1Application.class, args);
    }
}
複製代碼

application.yml

auth-server: http://localhost:8082/uaa # sso-server地址
server:
  context-path: /client1
  port: 8083
security:
  oauth2:
    client:
      client-id: merryyou1
      client-secret: merryyousecrect1
      user-authorization-uri: ${auth-server}/oauth/authorize #請求認證的地址
      access-token-uri: ${auth-server}/oauth/token #請求令牌的地址
    resource:
      jwt:
        key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所須要密鑰的地址
複製代碼

sso-client2

同sso-client1一致

效果以下: java

https://user-gold-cdn.xitu.io/2018/1/25/1612df517b92112f?w=1352&h=598&f=gif&s=1682736
https://user-gold-cdn.xitu.io/2018/1/25/1612df517b92112f?w=1352&h=598&f=gif&s=1682736

代碼下載

從個人 github 中下載,github.com/longfeizhen…git

相關文章
相關標籤/搜索