springsecurity 整合oauth2 jwt實現sso


spingsecurity+oauth2+jwt實現sso

前提

1、在閱讀此文時你應該有對oauth2的基本了解,及jwt的組成及springsecurity的基本配置。

2、使用RSA生成jwt及驗證

1.1 生成公鑰和和私鑰

(1)keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass  xuechengkeystore

 Keytool 是一個java提供的證書管理工具
-alias:密鑰的別名
-keyalg:使用的hash算法
-keypass:密鑰的訪問密碼
-keystore:密鑰庫文件名,xc.keystore保存了生成的證書
-storepass:密鑰庫的訪問密碼

 

這里有個小坑,新版本的keytool 不支持 設置密鑰的訪問密碼,我們在獲取秘鑰對時也不用去指定密碼

 

(2)導出公鑰

去這個網址http://slproweb.com/products/Win32OpenSSL.html 下載 Win64 OpenSSL v1.1.1h Light安裝后將其配置到環境變量中然后執行如下命令

keytool -list -rfc --keystore xc.keystore | openssl x509 -inform pem -pubkey

 

然后將導出的公鑰設為一行存為.txt文件

(3)將生成的證書文件和公鑰文件放在resource目錄下,使用如下代碼來測試生成jwt及驗證jwt

import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.test.context.junit4.SpringRunner;

import java.net.URL;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Administrator
 * @version 1.0
 **/

public class TestJwt {

    //創建jwt令牌
    @Test
    public void testCreateJwt(){
        //密鑰庫文件
        String keystore = "xc.keystore";
        //密鑰庫的密碼
        String keystore_password = "xuechengkeystore";

        //密鑰庫文件路徑
        ClassPathResource classPathResource = new ClassPathResource(keystore);
        //密鑰別名
        String alias  = "xckey";//密鑰工廠
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,keystore_password.toCharArray());
        //密鑰對(公鑰和私鑰)
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias); //獲取私鑰
        RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
        //jwt令牌的內容
        Map<String,String> body = new HashMap<>();
        body.put("name","itcast");
        String bodyString = JSON.toJSONString(body);
        //生成jwt令牌
        Jwt jwt = JwtHelper.encode(bodyString, new RsaSigner(aPrivate));
        //生成jwt令牌編碼
        String encoded = jwt.getEncoded();
        System.out.println(encoded);

    }

    //校驗jwt令牌
    @Test
    public void testVerify(){
        //公鑰
        String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnASXh9oSvLRLxk901HANYM6KcYMzX8vFPnH/To2R+SrUVw1O9rEX6m1+rIaMzrEKPm12qPjVq3HMXDbRdUaJEXsB7NgGrAhepYAdJnYMizdltLdGsbfyjITUCOvzZ/QgM1M4INPMD+Ce859xse06jnOkCUzinZmasxrmgNV3Db1GtpyHIiGVUY0lSO1Frr9m5dpemylaT0BV3UwTQWVW9ljm6yR3dBncOdDENumT5tGbaDVyClV0FEB1XdSKd7VjiDCDbUAUbDTG1fm3K9sx7kO1uMGElbXLgMfboJ963HEJcU01km7BmFntqI5liyKheX+HBUCD4zbYNPw236U+7QIDAQAB-----END PUBLIC KEY-----";
        //jwt令牌
        String jwtString = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaXRjYXN0In0.lQOqL1s4DpDHROUAibkz6EMf6hcM7HmTPgmg-SlkacVoQAV7y3XQ7LXxiua6SJlN_uNX_EFjzIshEg_kyy972DtymtRMc2NIO5HzIF5I4oQCxNPsJdhu6qQni6sTas3q0JbAarMZSajDX7HhzVSYWPQJCussA4e1r9oFxDcoAo6TEAXOW8gRHzNIygQz1yCj6mdf4UOHI070kRy7f3BdhmrUJdOuDIMoRBYS4WsEOibAU1UCNPaJAXpZC0ihrtdY7SCg1N43fimeFOHrfpLb6OmRF7v7uvGMgrhg9JIYDbJ6nbode5OJkNceRx8QUICre2yKAe0ctlvXO0REf6OpRA";
        //校驗jwt令牌
        Jwt jwt = JwtHelper.decodeAndVerify(jwtString, new RsaVerifier(publickey));
        //拿到jwt令牌中自定義的內容
        String claims = jwt.getClaims();
        System.out.println(claims);
    }
    @Test
    public void loadData(){
        String path = TestJwt.class.getClassLoader().getResource("publickey.txt").getPath();
        System.out.println(path);
    }
}

 

3、認證服務

1、目錄結構

JwtUser jwt令牌要存儲的對象,以及作為一個UserDetails 的實現類
package test.springsecurity.auth.DTO;

/**
 * jwt令牌中存儲的對象,可以附加自己想要的信息
 *
 * 將這個對象存到jwt中主要是JwtAccessTokenConverter這個對象的DefaultUserAuthenticationConverter來實現的
 *
 */
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

public class JwtUser extends User {

    private String  name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }
}
1、UserDetailsService 
package test.springsecurity.auth.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import test.springsecurity.auth.DTO.JwtUser;
import java.util.List;
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        String password = new BCryptPasswordEncoder().encode("123");

        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,queryAllOrder");

        JwtUser jwtUser = new JwtUser(s, password, authorities);

        jwtUser.setName("張三");

        return jwtUser;
    }

}

 

yaml文件配置文件

spring:
application: name: test
-auth server: port: 20004 eureka: client: service-url: defaultZone: http://127.0.0.1:20001/eureka instance: lease-renewal-interval-in-seconds: 5 # 5秒鍾發送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不發送就過期
#秘鑰相關的配置 ,你可以查看KeyProperties
encrypt: key-store: location: classpath:/xc.keystore secret: xuechengkeystore alias: xckey password: xuecheng
@ConfigurationProperties("encrypt")
public class KeyProperties 使用了這個配置
package test.springsecurity.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.annotation.Resource;
import java.security.KeyPair;

/**
 * 提供了
JwtAccessTokenConverter使用證書文件中的私鑰以及我們自定義的規則,將普通token轉為jwttoken
tokenStore tokenStore token的存儲方式
 *
 */
@Configuration
public class JwtConfig {


    //讀取密鑰的配置
    @Bean("keyProp")
    public KeyProperties keyProperties(){
        return new KeyProperties();
    }

    @Resource(name = "keyProp")
    private KeyProperties keyProperties;

    @Bean
    @Autowired
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    @Bean
    @Autowired
    public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory
                (keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
                .getKeyPair(keyProperties.getKeyStore().getAlias());
        converter.setKeyPair(keyPair);
        //這個類DefaultAccessTokenConverter負責jwt token的生成,我們可以自定義來添加我們想要的東西
        DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
        return converter;
    }
}

 CustomUserAuthenticationConverter責jwt token的生成

package test.springsecurity.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import test.springsecurity.auth.DTO.JwtUser;
import test.springsecurity.auth.service.UserDetailServiceImpl;

import java.util.LinkedHashMap;
import java.util.Map;

@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
    @Autowired
    UserDetailServiceImpl userDetailServiceImpl;

    @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        LinkedHashMap response = new LinkedHashMap();
        String name = authentication.getName();
        response.put("user_name", name);

        Object principal = authentication.getPrincipal();
        JwtUser jwtUser = null;
        if(principal instanceof  JwtUser){
            jwtUser = (JwtUser) principal;
        }else{
            //refresh_token默認不去調用userdetailService獲取用戶信息,這里我們手動去調用,得到 JwtUser
            UserDetails userDetails = userDetailServiceImpl.loadUserByUsername(name);
            jwtUser = (JwtUser) userDetails;
        }
        response.put("name", jwtUser.getName());

        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }

        return response;
    }


}

 

 WebSecurityConfig 

package test.springsecurity.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@Order(-1)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/userlogin","/userlogout","/userjwt");

    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    //采用bcrypt對密碼進行編碼
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .httpBasic().and()
                .formLogin()
                .and()
                .authorizeRequests().anyRequest().authenticated();

    }
}

 最核心的配置   AuthorizationServerConfigpackage test.springsecurity.auth.configimport org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.bootstrap.encrypt.KeyProperties;

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import test.springsecurity.auth.service.UserDetailServiceImpl; import javax.annotation.Resource; import javax.sql.DataSource; import java.security.KeyPair; @Configuration @EnableAuthorizationServer class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); //jwt令牌轉換器  @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired UserDetailServiceImpl userDetailServiceImpl; @Autowired AuthenticationManager authenticationManager; @Autowired TokenStore tokenStore; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient("client") .secret(bCryptPasswordEncoder.encode("123")) // .redirectUris("http://www.baidu.com") .redirectUris("http://localhost:20003/login") .accessTokenValiditySeconds(3600) .scopes("all") .authorizedGrantTypes("authorization_code","password","refresh_token"); } //授權服務器端點配置  @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.accessTokenConverter(jwtAccessTokenConverter) .authenticationManager(authenticationManager)//認證管理器 .tokenStore(tokenStore)//令牌存儲 .userDetailsService(userDetailServiceImpl);//用戶信息service  } //授權服務器的安全配置  @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { // oauthServer.checkTokenAccess("isAuthenticated()");//校驗token需要認證通過,可采用http basic認證  oauthServer.allowFormAuthenticationForClients() .passwordEncoder(new BCryptPasswordEncoder()) //是否可以訪問oauth/token_key :提供公有密匙的端點,使用 JWT 令牌時會使用 , 涉及的類 TokenKeyEndpoint // .tokenKeyAccess("permitAll()") // /oauth/check_token :用於資源服務器請求端點來檢查令牌是否有效, 涉及的類 CheckTokenEndpoint .checkTokenAccess("isAuthenticated()"); } 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM