zoukankan      html  css  js  c++  java
  • 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()");
        }
    
  • 相关阅读:
    E-Eating Together(POJ 3670)
    F-Dining Cows(POJ 3671)
    I-MooFest(POJ 1990)
    A-Apple Catching(POJ 2385)
    H-The Cow Lineup(POJ 1989)
    LOOPS(HDU 3853)
    Card Collector(HDU 4336)
    Help Me Escape (ZOJ 3640)
    Collecting Bugs(POJ 2096)
    c#大圣之路笔记——c# 从DataGrid中导出数据 Session
  • 原文地址:https://www.cnblogs.com/mofei12138/p/14022132.html
Copyright © 2011-2022 走看看