Spring Security源碼分析十二:Spring Security OAuth2基于JWT實(shí)現(xiàn)單點(diǎn)登錄

單點(diǎn)登錄(英語:Single sign-on,縮寫為 SSO),又譯為單一簽入,一種對于許多相互關(guān)連,但是又是各自獨(dú)立的軟件系統(tǒng),提供訪問控制的屬性。當(dāng)擁有這項(xiàng)屬性時,當(dāng)用戶登錄時,就可以獲取所有系統(tǒng)的訪問權(quán)限,不用對每個單一系統(tǒng)都逐一登錄。這項(xiàng)功能通常是以輕型目錄訪問協(xié)議(LDAP)來實(shí)現(xiàn),在服務(wù)器上會將用戶信息存儲到LDAP數(shù)據(jù)庫中。相同的,單一注銷(single sign-off)就是指,只需要單一的注銷動作,就可以結(jié)束對于多個系統(tǒng)的訪問權(quán)限。

Security OAuth2 單點(diǎn)登錄流程示意圖

  1. 訪問client1
  2. client1將請求導(dǎo)向sso-server
  3. 同意授權(quán)
  4. 攜帶授權(quán)碼code返回client1
  5. client1拿著授權(quán)碼請求令牌
  6. 返回JWT令牌
  7. client1解析令牌并登錄
  8. client1訪問client2
  9. client2將請求導(dǎo)向sso-server
  10. 同意授權(quán)
  11. 攜帶授權(quán)碼code返回client2
  12. client2拿著授權(quán)碼請求令牌
  13. 返回JWT令牌
  14. client2解析令牌并登錄

用戶的登錄狀態(tài)是由sso-server認(rèn)證中心來保存的,登錄界面和賬號密碼的驗(yàn)證也是sso-server認(rèn)證中心來做的(client1clien2返回token是不同的,但解析出來的用戶信息是同一個用戶)。

Security OAuth2 實(shí)現(xiàn)單點(diǎn)登錄

項(xiàng)目結(jié)構(gòu)

sso-server

認(rèn)證服務(wù)器

@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 授權(quán)表達(dá)式,訪問merryyou tokenkey時需要經(jīng)過認(rèn)證
     * @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 #請求認(rèn)證的地址
      access-token-uri: ${auth-server}/oauth/token #請求令牌的地址
    resource:
      jwt:
        key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所需要密鑰的地址

sso-client2

同sso-client1一致

效果如下:
[圖片上傳失敗...(image-1c6041-1516894354927)]

代碼下載

從我的 github 中下載,https://github.com/longfeizheng/sso-merryyou

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容