一、私钥公钥生成

1.使用java提供的keytool证书管理工具生成公钥私钥证书

创建一个文件夹,在该文件夹下执行如下命令:

keytool -genkeypair -alias kaikeba -keyalg RSA -keypass kaikeba -keystore kaikeba.jks -storepass kaikeba

  •  -alias:密钥的别名
  • -keyalg:使⽤的hash算法
  • -keypass:密钥的访问密码
  • -keystore:密钥库⽂件名
  • -storepass:密钥库的访问密码

获取jks 签名md5和公钥_java

密钥库证书文件:

获取jks 签名md5和公钥_获取jks 签名md5和公钥_02

 2.导出公钥

使用工具:openssl (这是一个加解密工具包,用来导出公钥信息)

安装 openssl:http://slproweb.com/products/Win32OpenSSL.html

下载完成之后,将openssl安装路径bin的路径(例如D:\OpenSSL-Win64\bin)配置到操作环境系统变量path中:

获取jks 签名md5和公钥_后端_03

 cmd进入kaikeba.jks文件所在目录执行如下命令

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

若是报“openssl不是内部或外部命令,也不是可运行的程序和批处理文件”,则重启一下电脑

重启之后再执行命令,就可以得到私钥和公钥

获取jks 签名md5和公钥_spring_04

 将上边的公钥拷贝到文本public.key文件中,然后放到资源服务器中

获取jks 签名md5和公钥_后端_05

 3.测试:

(1)在pom.xml文件中引入oauth2依赖

获取jks 签名md5和公钥_后端_06

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

 (2)将准备好的证书文件和公钥放至resourses目录下

获取jks 签名md5和公钥_后端_05

(3)测试代码:

package com.kaikeba.t31;

public class JwtTest {
    //⽣成⼀个jwt令牌
    @Test
    public void testCreateJwt() throws Exception {
        //证书⽂件
        String key_location = "kaikeba.jks";
        //密钥库密码
        String keystore_password = "kaikeba";
        //访问证书路径
        ClassPathResource resource = new ClassPathResource(key_location);
        //密钥⼯⼚
        KeyStoreKeyFactory keyStoreKeyFactory = new
                KeyStoreKeyFactory(resource, keystore_password.toCharArray());
        //密钥的密码,此密码和别名要匹配
        String keypassword = "kaikeba";
        //密钥别名
        String alias = "kaikeba";
        //密钥对(密钥和公钥)
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,
                keypassword.toCharArray());
        //私钥
        RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
        //定义payload信息
        Map<String, Object> tokenMap = new HashMap<String, Object>();
        tokenMap.put("id", "123");
        tokenMap.put("name", "malong");
        tokenMap.put("roles", "r01,r02");
        tokenMap.put("ext", "1");
        //⽣成jwt令牌
        Jwt jwt = JwtHelper.encode(new ObjectMapper().writeValueAsString(tokenMap), new RsaSigner(aPrivate));
        //取出jwt令牌
        String token = jwt.getEncoded();
        System.out.println(token);
    }

    //资源服务使⽤公钥验证jwt的合法性,并对jwt解码
    @Test
    public void testVerify() {
        //jwt令牌
        String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1hbG9uZyIsImlkIjoiMTIzIn0.NTaHZjQ91gugf2RnfLgexNi_uFlvRxkNexxDSldW8niW18I4Y_iOEp76FvODR28UwFJuaO38ZGxbroHLCq8wt4oQb6xKTPu_eRqdUmk2cJAno2i43w3ynUMCdecOHFPBxxWMfVKEGMcgfKKS81bXjrf-0EL0WEA9i0u03Fuu87dg8SI8aZkH1VP60X-CAuHOICLTB2AjOijWgX1OQr52kIiQ4OQ_C_16tRvsSMxarQ_RkjdTZ-3UD2AF6mXPr8rGuJR1ZV-0xjSm4QDej2pKUYS8Qkj_6KUTI_CRTNN9DLCBfRMO71ZG8WbeFfVP7PxOO5s986hPU_9ywXcC-sqMpA";
        //公钥
        String publickey = "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlfg3iq3+hsUJKi/MWXrd\n" +
                "/1xxVzdfMbg8Jk7hxLSgRtjb4XzptUqXaRy4dq+SYTmr+An6qD0qnDDBV4MdrEW/\n" +
                "Gk9o31KSHRKLVpv3NCd2QWJCtuTPLBJYOneHDGifiTR8B4nA7FMYuT83FWH/WQYy\n" +
                "mUQSp+nEwrNfF/W0glAIlWitFABuPXS6dkh8veoTPFNWTaewp7E4lv+bGCQBlMZw\n" +
                "4Lcbw8fVWnUZ2s9ByjtFGFHAof5QZA7HTDGs/flXVkgm8UvSRwB2fo8ibBU0aLD6\n" +
                "FYaOmtix9NocxISnGGjdlJ7rVSXygh/TkE2ZFAtN/2E1C1CXd3wWJapvdRqjZtVl\n" +
                "twIDAQAB\n" +
                "-----END PUBLIC KEY-----";
        //校验jwt
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
        //获取jwt原始内容
        String claims = jwt.getClaims();
        System.out.println(claims);
        try {
            Map<String, String> map = new ObjectMapper().readValue(claims, Map.class);
            System.out.println(map.get("user_name"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行上面的testCreateJwt()测试,里面的JwtHelper.encode方法就是生成令牌的方法。在console中显示的就是令牌。

获取jks 签名md5和公钥_java_08

 将此令牌复制粘贴到测试方法testVerify()中的token中,将公钥信息粘贴到publickey变量中,然后执行该测试方法。

执行成功:

获取jks 签名md5和公钥_获取jks 签名md5和公钥_09

 二、用户角色管理:

获取jks 签名md5和公钥_开发语言_10

需求分析:授权中心在发令牌的时候需要确认身份,要判断用户的用户名、密码、角色等信息进行处理和查询,验证用户的用户名、密码,得到用户角色等信息,并存储在jwt里面,然后才能交给springSecurity进行授权

实现:基于RBAC模型

RBAC(基于角色的权限控制role base access control)是一种设计模式,是用来设计和管理权限相关数据的一种模型

1.搭建授权中心工程

(1)配置pom.mxl文件

获取jks 签名md5和公钥_开发语言_11

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>t31-parent</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>t31-auth-center</artifactId>
    <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--nacos-config-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
        <!--oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.kaikeba</groupId>
            <artifactId>t31-admin-instance</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

(2)配置文件

获取jks 签名md5和公钥_spring_12

 

bootstrap.yml

spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yaml
        extension-configs[0]:
          data-id: common.yaml
          refresh: true
        extension-configs[1]:
          data-id: db.yaml
          refresh: true
  # 多个接口上的@FeignClient(“相同服务名”)会报错,overriding is disabled。
  # 设置 为true ,即 允许 同名
  main:
    allow-bean-definition-overriding: true

 application.yml

spring:
  application:
    name: auth-center
  profiles:
    active: dev

(3)将之前生成的密码对文件拷贝进来

获取jks 签名md5和公钥_获取jks 签名md5和公钥_13

 (4)编写启动类

package com.kaikeba.t31;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class AuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

2.配置类

(1)OAuth2配置类

package com.kaikeba.t31.config;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);
    @Autowired
    @Qualifier("authenticationManagerBean")
    AuthenticationManager authenticationManager;
    @Autowired
    private DataSource dataSource;
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new
                ClassPathResource("kaikeba.jks"), "kaikeba".toCharArray());//证书路径和密钥库密码
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("kaikeba"));//密钥别名
        return converter;
    }
    @Bean
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //配置通过表oauth_client_details,读取客户端数据
        clients.withClientDetails(clientDetailsService());
    }
/**
 * 配置token service和令牌存储⽅式(tokenStore
 * @param endpoints
 * @throws Exception
 */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    //tokenStore
    endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager);
    //tokenService
    DefaultTokenServices tokenServices = new DefaultTokenServices();
    tokenServices.setTokenStore(endpoints.getTokenStore());
    tokenServices.setSupportRefreshToken(false);
    tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
    tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
    tokenServices.setAccessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(30)); // 30天
    endpoints.tokenServices(tokenServices);
}
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许表单认证
        security.allowFormAuthenticationForClients()
                //放⾏oauth/token_key(获得公钥)
                .tokenKeyAccess("permitAll()")
                //放⾏ oauth/check_token(验证令牌)
                // .checkTokenAccess("isAuthenticated()");
                .checkTokenAccess("permitAll()");
    }
}

(2)SpringSecurity配置类

package com.kaikeba.t31.config;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    /**
     * 注⼊⾃定义UserDetailService,读取rbac数据
     */
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().anyRequest()
                .and().authorizeRequests().antMatchers("/oauth/**").permitAll();//开放/oauth/开头的所有请求
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //注⼊⾃定义的UserDetailsService,采⽤BCrypt加密
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

3.UserDetailsService

这个组件是扩展spring security最重要的一个组件。作用是从数据库中读取rbac数据,用户、密码、角色数据返回UserDetails,交给Spring Security框架匹配验证,办法令牌等操作。

获取jks 签名md5和公钥_获取jks 签名md5和公钥_14

 

package com.kaikeba.t31.service.impl;


@Service
public class UserDetailServiceImpl implements UserDetailsService {
    private static final Logger logger =
            LoggerFactory.getLogger(UserDetailServiceImpl.class);
    @Autowired
    UserClient userClient;
    @Autowired
    PasswordEncoder passwordEncoder;//BCryptPasswordEncoder
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AdminUser user = userClient.getByUserName(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (user != null) {
            logger.debug("current user = " + user);
            //获取⽤户的授权
            List<AdminRole> roles = userClient.selectRolesByUserId(user.getId());
            //声明授权⽂件
            for (AdminRole role : roles) {
                if (role != null && role.getName() != null) {
                    GrantedAuthority grantedAuthority = new
                            SimpleGrantedAuthority("ROLE_" + role.getName());//spring Security中权限名称必须满⾜ROLE_XXX
                    grantedAuthorities.add(grantedAuthority);
                }
            }
        }
        logger.debug("granted authorities = " + grantedAuthorities);
        return new User(user.getUserName(), user.getPassword(), grantedAuthorities);
    }
}

 其中读取数据的UserFeign代码如下

package com.kaikeba.t31.client;

@FeignClient(name = "admin-service", fallback = UserClient.UserClientFallback.class)
public interface UserClient extends UserApi {
    @Component
    @RequestMapping("/fallback")//这个可以避免容器中requestMapping重复
    class UserClientFallback implements UserClient {
        private static final Logger LOGGER = LoggerFactory.getLogger(UserClientFallback.class);

        @Override
        public AdminUser getByUserName(String userName) {
            LOGGER.info("异常发生,进入fallback方法");
            return null;
        }

        @Override
        public List<AdminRole> selectRolesByUserId(Long id) {
            LOGGER.info("异常发生,进入fallback方法");
            return null;
        }
    }
}
package com.kaikeba.t31.admin.api;

public interface UserApi {
    @ApiOperation("通过登录获得用户")
    @GetMapping("/get/{userName}")
    AdminUser getByUserName(@PathVariable("userName") String userName);


    @ApiOperation("通过用户ID获得角色")
    @GetMapping("/select-roles/{id}")
    List<AdminRole> selectRolesByUserId(@PathVariable("id") Long id);

}

配置完了之后就可以启动授权中心了。

4.搭建资源中心

(1)JwtConfig.java 创建TokenStore配置使用公钥验证令牌

package com.kaikeba.t31.config;

@Configuration
public class JwtConfig {
    public static final String public_cert = "public.key";
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Bean
    @Qualifier("tokenStore")
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource(public_cert);
        String publicKey;
        try {
            publicKey = new
                    String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
// 设置校验公钥
        converter.setVerifierKey(publicKey);
// 设置证书签名密码,否则报错
        converter.setSigningKey("kaikeba");
        return converter;
    }
}

(2)ResourceServerConfiguration 配置资源服务器

package com.kaikeba.t31.config;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                // .antMatchers("/**").permitAll();
                .antMatchers("/user/**").permitAll()
                .antMatchers("/book/**").hasRole("ADMIN") //⽤于测试
                .antMatchers("/**").authenticated();
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }
}

(3)配置security.yaml

用于 资源服务器在启动的时候都需要对公钥进行一次验签,验签通过才能启动。本工程在nacos中进行此配置。

获取jks 签名md5和公钥_后端_15