springsecurity +oauth2.0
学习本文之前你应该会熟练使用Springboot,并对 SpringSecurity
和 OAuth2.0
有所理解,如有需要请参考下面的一些内容,简单理解下相关知识
Spring Security是一个功能强大、高度可定制的身份验证和访问控制框架。它用于保护基于Spring的应用程序。Spring Security是一个专注于向Java应用程序提供身份验证和授权的框架。
与所有Spring项目一样,Spring安全的真正威力在于它可以很容易地扩展以满足定制需求。
OAuth 2.0是用于授权的行业标准协议。OAuth2.0注重客户端开发人员的简单性,同时为Web应用程序、桌面应用程序、移动电话和客厅设备提供特定的授权流。
更多请参考 OAuth2.0
OAuth2.0模式
模式 | 应用场景 | 描述 |
---|---|---|
授权码(Auth Code) | 成功后跳转其他页面,如第三方微信登录等 | |
简化模式(implicit) | 不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌 | |
密码模式(password credentials) | web页面用户名密码登录 | |
客户端模式(client credentials) | 主要针对openapi,使用apikey和secretkey方式 |
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。更多请参考 JWT入门教程
核心pom依赖如下:
<!-- 注意是starter,自动配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 不是starter,手动配置 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 将token存储在redis中 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
创建一个rest接口用于后面测试资源
@Slf4j @RestController public class TestSecurityController { @GetMapping("/product/{id}") public String getProduct(@PathVariable String id) { //for debug Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return "product id : " + id; } @GetMapping("/order/{id}") public String getOrder(@PathVariable String id) { //for debug Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return "order id : " + id; } }
很多文章写的特别复杂,其实主要的内容也就分为下面几步
AuthorizationServerConfigurerAdapter
需要自定义授权服务器,继承 AuthorizationServerConfigurerAdapter
,详细代码如下
@Configuration @EnableAuthorizationServer public class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter { private static final String DEMO_RESOURCE_ID = "order"; @Autowired AuthenticationManager authenticationManager; @Autowired RedisConnectionFactory redisConnectionFactory; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //配置两个客户端,一个用于password认证一个用于client认证 String secret = new BCryptPasswordEncoder().encode("123456");////对密码进行加密 clients.inMemory().withClient("client_1") .resourceIds(DEMO_RESOURCE_ID) .authorizedGrantTypes("client_credentials", "refresh_token") .scopes("select") .authorities("client") .secret(secret) .and().withClient("client_2") .resourceIds(DEMO_RESOURCE_ID) .authorizedGrantTypes("password", "refresh_token") .scopes("select") .authorities("client") .secret(secret); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(new RedisTokenStore(redisConnectionFactory)) .authenticationManager(authenticationManager); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { //允许表单认证 oauthServer.allowFormAuthenticationForClients(); } }
ResourceServerConfigurerAdapter
同上,需要实现自己的资源服务器,继承 ResourceServerConfigurerAdapter
,详细代码如下
@Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { private static final String DEMO_RESOURCE_ID = "order"; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(DEMO_RESOURCE_ID).stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off http // Since we want the protected resources to be accessible in the UI as well we need // session creation to be allowed (it's disabled by default in 2.0.6) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .requestMatchers().anyRequest() .and() .anonymous() .and() .authorizeRequests() // .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')") .antMatchers("/order/**").authenticated();//配置order访问控制,必须认证过后才可以访问 // @formatter:on } }
@Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean @Override protected UserDetailsService userDetailsService(){ InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); String pwd = new BCryptPasswordEncoder().encode("123456");//对密码进行加密 manager.createUser(User.withUsername("user_1").password(pwd).authorities("USER").build()); manager.createUser(User.withUsername("user_2").password(pwd).authorities("USER").build()); return manager; } @Override protected void configure(HttpSecurity http) throws Exception { http .requestMatchers().anyRequest() .and() .authorizeRequests() .antMatchers("/oauth/*").permitAll(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager = super.authenticationManagerBean(); return manager; } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
我们设计的是 product
服务可以匿名访问,而 order
服务需要签名才可以访问,验证如下:
利用postman进行 post 访问 http://localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=123456
获取如下结果
{ "access_token": "c2340190-48f3-4291-bb17-1e4d51bcb284", "token_type": "bearer", "refresh_token": "03ee113c-a942-452a-9918-7ffe24472a7f", "expires_in": 40399, "scope": "select" }
同样利用postman的POST方式访问 http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456
结果如下
{ "access_token": "05a4e614-f34b-4c83-9ec1-89ea55c0afd2", "token_type": "bearer", "expires_in": 40396, "scope": "select" }
http://localhost:8080/product/1
得到如下数据 product id : 1
http://localhost:8080/order/1
,返回数据如下 <oauth> <error_description> Full authentication is required to access this resource </error_description> <error>unauthorized</error> </oauth>
验证结果,说明order服务需要签名才可以访问,接下来,我们输入签名访问order服务。
我们分别利用上面password模式获取的token,访问 http://localhost :8080/order/1?access_token=c2340190-48f3-4291-bb17-1e4d51bcb284
得到数据 order id : 1
通用利用client模式获取的token,访问 http://localhost :8080/order/1?access_token=05a4e614-f34b-4c83-9ec1-89ea55c0afd2
同样可以得到 order id : 1
分支为 springsecurity-oauth2-1
,master为最终代码
https://github.com/liangliang1259/fast-cloud-examples/tree/springsecurity-oauth2-1
http://blog.didispace.com/spr...