Oauth2入门

授权流程

 
角色

授权模式

入门案例
授权服务器
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>pre.cg</groupId>
    <artifactId>auth-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>auth-server</name>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
server:
  port:
    8888
@SpringBootApplication
@EnableResourceServer
public class AuthServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthServerApplication.class, args);
    }
}
 
开启授权服务
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    private static final String CLIENT_ID = "cms";
    private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
    private static final String SCOPE_READ = "read";
    private static final String SCOPE_WRITE = "write";
    private static final String TRUST = "trust";
    private static final String USER ="user";
    private static final String ALL = "all";
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30*60;
    private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 30*60;
    // 密码模式授权模式
    private static final String GRANT_TYPE_PASSWORD = "password";
    //授权码模式
    private static final String AUTHORIZATION_CODE = "authorization_code";
    //refresh token模式
    private static final String REFRESH_TOKEN = "refresh_token";
    //简化授权模式
    private static final String IMPLICIT = "implicit";
    //客户端模式
    private static final String CLIENT_CREDENTIALS="client_credentials";
    //指定哪些资源是需要授权验证的
    private static final String RESOURCE_ID = "resource_id";
	// 客户端信息配置
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 使用内存存储
                .inMemory()
                //标记客户端id
                .withClient(CLIENT_ID)
                //客户端安全码
                .secret(SECRET_CHAR_SEQUENCE)
                //为true 直接自动授权成功返回code
                .autoApprove(true)
                .redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri
                //允许授权范围
                .scopes(ALL)
                //token 时间秒
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                //刷新token 时间 秒
                .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS)
                //允许授权类型
                .authorizedGrantTypes(AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT, GRANT_TYPE_PASSWORD, CLIENT_CREDENTIALS);
    }
	//token存储方式
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 使用内存保存生成的token
        endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore());
    }
    @Bean
    public TokenStore memoryTokenStore() {
        // 最基本的InMemoryTokenStore生成token
        return new InMemoryTokenStore();
    }
    /**
     * 认证服务器的安全配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 开启/oauth/token_key验证端口认证权限访问
                .tokenKeyAccess("permitAll()")
                //  开启/oauth/check_token验证端口认证权限访问
//                .checkTokenAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                //允许表单认证
                .allowFormAuthenticationForClients();
    }
}
 
Spring Security配置类
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
	//登录用户配置
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {    //auth.inMemoryAuthentication()
        auth.inMemoryAuthentication()
                .withUser("lxs")
                .password("{noop}123")
                .roles("admin");
    }
	//静态资源放行配置
    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/asserts/**");
        web.ignoring().antMatchers("/favicon.ico");
    }
	//spring security 权限配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http   // 配置登录页并允许访问
                .formLogin().permitAll()
                // 配置Basic登录
                //.and().httpBasic()
                // 配置登出页面
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                // 配置允许访问的链接
                .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**", "/api/**").permitAll()
                // 其余所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                // 关闭跨域保护;
                .and().csrf().disable();
    }
}
 

资源服务器
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>pre.cg</groupId>
    <artifactId>cms</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cms</name>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
server:
  port: 8084
  servlet:
    context-path: /cms
@SpringBootApplication
@EnableResourceServer
public class CmsApplication {
    public static void main(String[] args) {
        SpringApplication.run(CmsApplication.class, args);
    }
}
 
资源服务器配置类
@Configuration
public class Oauth2ResourceServerConfiguration extends
    ResourceServerConfigurerAdapter {
  private static final String CHECK_TOKEN_URL = "http://localhost:8888/oauth/check_token";
  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    RemoteTokenServices tokenService = new RemoteTokenServices();
//    tokenService.setRestTemplate(restTemplate);
    tokenService.setCheckTokenEndpointUrl(CHECK_TOKEN_URL);
    tokenService.setClientId("cms");
    tokenService.setClientSecret("secret");
//    DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
//    defaultAccessTokenConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());
//    tokenService.setAccessTokenConverter(defaultAccessTokenConverter);
    resources.tokenServices(tokenService);
  }
}
 
spring security配置类
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/**").authenticated();
    // 禁用CSRF
    http.csrf().disable();
  }
}
 
测试用controller
@RestController
public class HelloController {
    @GetMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication;
    }
    @GetMapping("/index")
    public String index() {
        return "index";
    }
}
 

Oauth2授权模式

授权码模式

- 认证服务授权码配置
 

-  
访问授权链接,在浏览器访问就可以,授权码模式response_type参数传code:
Get请求:
http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code 

 因为没登录,所以会返回SpringSecurity的默认登录页面,具体代码是 http .formLogin().permitAll(); ,如果要弹窗登录的,可以配置 http.httpBasic(); ,这种配置是没有登录页面的,自定义登录页面可以这样配置http.formLogin().loginPage("/login").permitAll() ,参考OAuth2Config代码
 
 
 
- 拿到授权码后,申请令牌。 使用Postman申请授权码
 

此链接需要使用 http Basic认证。 什么是http Basic认证? http协议定义的一种认证方式,将客户端id
和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编 码,放在header中请求服务端,
一个例子: Authorization:Basic WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA= 是用户名:密码的base64编码。认证失败服务端返回 401 Unauthorized
 

 
 客户端Id和客户端密码会匹配数据库oauth_client_details表中的客户端id及客户端密码
 
 
-  
Spring Security Oauth2提供校验令牌的端点
Get:
http://localhost:8888/oauth/check_token?token=171ce96e-7492-4a27-becd- 8ccbdc69666b 

- 使用令牌
 

密码模式

 
-  
申请令牌




客户端Id和客户端密码会匹配数据库oauth_client_details表中的客户端id及客户端密码


 -  
令牌校验
Get:
http://localhost:8888/oauth/check_token?token=1e628350-5711-4983-9b10- da7a7e8b9558 

- 使用令牌

 
简化模式
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此称简化模式。简化模式是相对于授权码模式而言的
 
 
- 认证服务授权码配置
 

-  
申请令牌
Get请求:
http://localhost:8888/oauth/authorize?client_id=cms&redirect_uri=http://127.0.0.1:8084/cms/login&response_type=token&s cope=all 

 
-  
令牌校验
Get:
http://localhost:8888/oauth/check_token?token=171ce96e-7492-4a27-becd- 8ccbdc69666b 

- 使用令牌
 

简化模式和授权码模式的区别
授权码模式User-agent(浏览器)只是持有授权码(code)使用授权码获得令牌,授权码,只能校验一次,这样即使授权码泄露,令牌相对安全,而简化模式由user agent(浏览器),直接持有令牌,相对不安全
客户端模式
客户端模式(client credentials):客户端模式(client credentials)适用于没有前端的命令行应用,即在命令行下请求令牌
 
- 认证服务授权码配置
 

-  
申请令牌
post请求:
http://localhost:8888/oauth/token?client_id=cms&client_secret=secret&grant_type=client_credentials&scope=all 

 
-  
令牌校验
Get:
http://localhost:8888/oauth/check_token?token=171ce96e-7492-4a27-becd- 8ccbdc69666b 

- 使用令牌
 

 
令牌存储方式

JWT方式存储token 案例
认证服务器采用JwtTokenStore处理token,也就是认证服务器器不存储token,实现纯粹的无状态token管理,同时应该spring security oauth2的脚本规范,使用数据库方式存储客户端信息
 
<?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>legou-parent</artifactId>
        <groupId>com.lxs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>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-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</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.lxs</groupId>
            <artifactId>legou-security-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>cert</nonFilteredFileExtension>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
@SpringBootApplication
@EnableResourceServer
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class  AuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}
 
yml
spring:
  application:
    name: auth-center
  main:
    allow-bean-definition-overriding: true
server:
  port: 9098
logging:
  level:
    org.springframework.security: DEBUG    
@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 {
        com.lxs.legou.security.po.User user = userClient.getByUserName(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (user != null) {
            logger.debug("current user = " + user);
            //获取用户的授权
            List<Role> roles = userClient.selectRolesByUserId(user.getId());
            //声明授权文件
            for (Role 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 org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), grantedAuthorities);
    }
}
 
oauth2配置类
@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//声明 ClientDetails实现
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }
    @Override//配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //直接读取数据库,需要保证数据库配置有客户端信息(oauth_client_details),否则资源服务器无法获取认证数据
        clients.withClientDetails(clientDetailsService());
    }
    /**
     * 使用JwtTokenStore实现无状态存储令牌
     */
    @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;
    }
    /**
     *配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services),还有token的存储方式(tokenStore)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager);
        // 配置tokenServices参数
        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()");
    }
}
 
spring security配置类
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
	@Autowired
    private UserDetailsService userDetailsService;//注入自定义userdetailservice(com.service.auth.serviceauth.service.impl.UserDetailServiceImpl)
	
    @Bean
    PasswordEncoder passwordEncoder() {
    	return new BCryptPasswordEncoder();
        //return PasswordEncoderFactories.createDelegatingPasswordEncoder();//兼容多种密码的加密方式
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().anyRequest()//所有请求都加入HttpSecurity(多个HttpSecurity过滤)
                .and().authorizeRequests().antMatchers("/oauth/**").permitAll();//开放/oauth/开头的所有请求
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());//注入自定义的UserDetailsService,采用BCrypt加密
    }
}
 
非对称加密

 
 
资源服务授权流程
-  
传统授权流程

 -  
公钥私钥授权流程


 
生成私钥公钥
用keytool工具生成公钥私钥证书,keytool工具是JDK自带的工具
-  
创建一个文件夹,在该文件夹下执行如下命令行:
keytool -genkeypair -alias makeFile-keyalg RSA -keypass makeFile-keystore makeFile.jks -storepass makeFile
 

-  
查询证书信息
keytool -list -keystore kaikeba.jks
 -  
删除
keytool -delete -alias kaikeba -keystore kaikeba.jsk
 
导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息
 
 cmd进入kaikeba.jks文件所在目录执行如下命令(如下命令在windows下执行,会把-变成中文方式,将它改成英文的-):
keytool -list -rfc --keystore kaikeba.jks | openssl x509 -inform pem -pubkey
 

JWT令牌
使用私钥生成令牌
//生成一个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", "mrt");
        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=" + token);
    }
 

解析令牌
	//资源服务使用公钥验证jwt的合法性,并对jwt解码
    @Test
    public void testVerify() {
        //jwt令牌
        String token
                = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1ydCIsImlkIjoiMTIzIn0.OXzFObxUq35--qgvBy4mnBXx-f9mfpYMczTfAfH7yHM05W-oJ6RPmLPonZsFlZMd8JBdLm6iz_TN6b4ynO0heCBsyML2ZLx0sxhgE28mhztDXj2GHbWu3kwsRzU9Pbgy-CO3FIG0Iw-aIkFSivaaLsCju5oOLxGB825ueI5hM58sPLLykZPAaU6DcVY3X1sfpWDIQ7G7JkCoP3rH385Vcmg1VBJVIwVxEn4TXHtWqre9lgK-T7D4zXlhScB57gv9OfcbebNm8tI2Rew1IHmOCeKf5CKAiSCv5d26LhLPKqvGBQ5Cy67JM58X2T-4LvgQeQR6TZmiiSr7fLnEkNe9KQ";
        //公钥
        String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtYpjt7NtpS1B51x6PUK7ryvKySK4VQi7KUCGBm6kisErNM+FwdgKMbpQxTtWoYyXfQsWwuhBW45+uF+Z5DUDaLtHlMV55eA5fkGLFZ1F9ppZC+2Etsy1CyPqA0Mx8R0/HbMB1no4KTlQpqST7JjCdtwLWqUd68zDlfToIsWB1fHuYHbH/DCGUBmZb+16805/SjWkYvj3B6F+WJ8Gm47/OJBH+wo7k4GWZ7OXdMcNnYWMyBfa4abjo7cxjoHL2fDanS6And4Sh3cZEJde4WgXsEktvR/EaZR7CeQzwzOg47+5cCcFSYgmVfpDyLsBnFkG3WFs/qZ3yPzy+DQKLIF2wIDAQAB-----END PUBLIC KEY-----";
        //校验jwt
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
        //获取jwt原始内容
        String claims = jwt.getClaims();
        System.out.println(claims);
        //jwt令牌
        String encoded = jwt.getEncoded();
        System.out.println(encoded);
    }
 

JWT方式存储token 完善
security-service微服务
<!--oauth依赖--> 
<dependency> 
	<groupId>org.springframework.cloud</groupId> 
	<artifactId>spring-cloud-starter-oauth2</artifactId> 
</dependency>
security:
  oauth2:
    resource:
      jwt:
        key-uri: http://localhost:9098/oauth/token_key	#如果使用JWT,可以获取公钥用于 token 的验签
 
配置使用公钥校验令牌
@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;
    }
}
 
配置资源服务器
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/**", "/security/user/**").permitAll()
                .antMatchers("/book/**").hasRole("ADMIN") //用于测试
                .antMatchers("/**").authenticated();
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }
}
 
创建测试用的Controller
@RestController
public class TestEndPointController {
    Logger logger = LoggerFactory.getLogger(TestEndPointController.class);
    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        return "product id : " + id;
    }
    @GetMapping("/order/{id}")
    public String getOrder(@PathVariable String id) {
        return "order id : " + id;
    }
    @GetMapping("/book/{id}")
    public String getBook(@PathVariable String id) {
        return "book id : " + id;
    }
    @GetMapping("/anno/{id}")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public String getAnno(@PathVariable String id) {
        return "admin id :" + id;
    }
    @RequestMapping("/hello")
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    public String hello() {
        return "hello you ...";
    }
    @GetMapping("/getPrinciple")
    public OAuth2Authentication getPrinciple(OAuth2Authentication oAuth2Authentication, Principal principal, Authentication authentication) {
        logger.info(oAuth2Authentication.getUserAuthentication().getAuthorities().toString());
        logger.info(oAuth2Authentication.toString());
        logger.info("principal.toString() " + principal.toString());
        logger.info("principal.getName() " + principal.getName());
        logger.info("authentication: " + authentication.getAuthorities().toString());
        return oAuth2Authentication;
    }
}
 

SpringSecurity权限控制
- UserDetailServiceImpl
 

 在每个微服务中,需要获取用户的角色,然后根据角色识别是否允许操作指定的方法,Spring Security中定义了四个支持权限控制的表达式注解,分别是 @PreAuthorize 、 @PostAuthorize 、 @PreFilter 和@PostFilter 。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。在需要控制权限的方法上,我们可以添加@PreAuthorize 注解,用于方法执行前进行权限检查,校验用户当前角色是否能访问该方法。
- 开启@PreAuthorize
 
ResourceServerConfiguration类上添加 @EnableGlobalMethodSecurity 注
 解,用于开启@PreAuthorize的支持
 
- ** TestEndPointController 类的测试方法上添加权限控制注解 @PreAuthorize**
 

- 同时也可以在资源服务配置类中配置,进行权限控制
 

JWT方式存储token 再完善


-  
legou-security.yml
security:
oauth2:
resource:
jwt:
key-uri: http://localhost:9098/oauth/token_key #如果使用JWT,可以获取公钥用于 token 的验签
client:
access-token-uri: http://localhost:9098/oauth/token #令牌端点
user-authorization-uri: http://localhost:9098/oauth/authorize #授权端点
client-id: client
client-secret: 123456
grant-type: password
scope: read,write -  
BCryptPasswordEncoder加密加密组件,处理密码
@Configuration
public class SecurityConfig {@Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); //return PasswordEncoderFactories.createDelegatingPasswordEncoder();//兼容多种密码的加密方式 }}
 -  
service密码加密
@Override
@Transactional(readOnly = false)
public boolean saveOrUpdate(User entity) {
//添加时,设置lock=false
if (null == entity.getId()){
entity.setLock(false);
}entity.setPassword(passwordEncoder.encode(entity.getPassword()));// passwordHelper.encryptPassword(entity); //加密md5(md5(password,salt))
boolean result = super.saveOrUpdate(entity); ((UserDao) getBaseMapper()).deleteRoleByUser(entity.getId()); Long[] roleIds = entity.getRoleIds(); if (null != roleIds) { for (Long roleId : roleIds) { ((UserDao) getBaseMapper()).insertRoleAndUser(roleId, entity.getId()); } } return result; } -  
userController
@RestController
@RequestMapping(value = “/user”)
public class UserController extends BaseController<IUserService, User> {@Autowired private OAuth2ClientProperties oAuth2ClientProperties; //client_id, client_secret @Autowired private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails; //access-token-uri, grant_type @Bean public RestTemplate restTemplate() { return new RestTemplate(); } @Autowired private RestTemplate restTemplate; //返回access_token信息,前端保存并用来访问资源 @RequestMapping("/login") public ResponseEntity<OAuth2AccessToken> login(String username, String password) { //1:验证用户 User user = service.getUserByUserName(username); if (null == user) { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } if (!BPwdEncoderUtil.matches(password, user.getPassword())) { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } //2:使用restTemplate发送请求到授权服务器,申请令牌 //请求头 "basic auth" String client_secret = oAuth2ClientProperties.getClientId() + ":" + oAuth2ClientProperties.getClientSecret(); client_secret = "Basic " + Base64.getEncoder().encodeToString(client_secret.getBytes()); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", client_secret); //请求参数 MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.put("username", Collections.singletonList(username)); map.put("password", Collections.singletonList(password)); map.put("grant_type", Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType())); map.put("scope",oAuth2ProtectedResourceDetails.getScope()); //HttpEntity(请求参数,头。。。) HttpEntity httpEntity = new HttpEntity(map, headers); return restTemplate.exchange(oAuth2ProtectedResourceDetails.getAccessTokenUri(), HttpMethod.POST, httpEntity, OAuth2AccessToken.class); }}
 










