这是我参与「掘金日新计划 · 6 月更文挑战」的第29天 ,点击查看活动详情
一、创建项目
创建authcode-server文件夹,打开文件夹
二、创建资源服务器
1、创建授权服务器模块
创建Spring Boot模块:authcode-rs
2、配置pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置application.properties
server.port=8080
4、创建实体类
package com.atguigu.authcoders.api;
@Data
public class UserInfo {
private String name;
private String email;
}
5、创建api
package com.atguigu.authcoders.api;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
@RequestMapping("/api/userinfo")
public ResponseEntity<UserInfo> getUserInfo() {
String username = "helen";
String email = username + "@atguigu.com";
UserInfo userInfo = new UserInfo();
userInfo.setName(username);
userInfo.setEmail(email);
return ResponseEntity.ok(userInfo);
}
}
6、启动资源服务器
7、访问资源api
http://localhost:8080/api/userinfo\
得到资源数据
{"name":"helen","email":"helen@atguigu.com"}
三、加入Spring Cloud Security
1、配置pom
<dependencyManagement>
<dependencies>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
......
<!--微服务安全-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
</dependencies>
2、修改api
package com.atguigu.authcoders.api;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
@RequestMapping("/api/userinfo")
public ResponseEntity<UserInfo> getUserInfo() {
User user = (User) SecurityContextHolder
.getContext()
.getAuthentication()
.getPrincipal();
String username = user.getUsername();
String email = username + "@atguigu.com";
UserInfo userInfo = new UserInfo();
userInfo.setName(username);
userInfo.setEmail(email);
return ResponseEntity.ok(userInfo);
}
}
3、配置默认的用户名和密码
在application.properties中配置
spring.security.user.name=xiaolu
spring.security.user.password=xyz
4、重启资源服务器
\
5、访问资源api
出现登录页面:输入前面刚刚配置的用户名和密码可以访问api接口
\
四、配置资源服务器
1、添加配置文件
package com.atguigu.authcoders.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* 资源服务配置
*/
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()//所有请求
.authenticated()//都要进行OAuth2验证
.and() //
.requestMatchers() //缩小匹配范围:
.antMatchers("/api/**");//对/api/*资源进行OAuth2验证
}
}
2、重启资源服务器
\
3、访问资源api
提示未授权:必须使用access_token才能访问到资源
\
五、创建授权服务器
1、创建授权服务器模块
创建Spring Boot模块:authcode-as
2、配置pom
<dependencyManagement>
<dependencies>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--微服务安全-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置application.properties
server.port=8081
4、添加OAuth2配置文件
package com.atguigu.authcodeas.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.provisioning.InMemoryUserDetailsManager;
/**
* 授权服务器配置
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
AuthorizationServerConfigurerAdapter {
// 添加商户信息
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()//token等信息存入内存
.withClient("clientapp")//客户端id
.secret(passwordEncoder().encode("123456"))//客户端secret
.redirectUris("http://localhost:8082/callback")//回调客户应用服务器
// 授权码模式
.authorizedGrantTypes("authorization_code")
.scopes("read_userinfo", "read_contacts");//细分权限
}
// 设置添加用户信息,正常应该从数据库中读取
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
.authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
.authorities("ROLE_USER").build());
return userDetailsService;
}
@Bean //password加密的方式 相当于把PasswordEncoder类对象 注册到容器中
PasswordEncoder passwordEncoder() {
// 加密方式
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
}
5、添加Security配置文件
package com.atguigu.authcodeas.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.stereotype.Component;
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 授权中心管理器
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 拦截所有请求,使用httpBasic方式登录
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
}
}
5、启动授权服务器
验证token 刷新token 都有日志\
\
6、获取授权码
请求认证服务获取授权码:
Get请求:
\
参数列表如下:
-
response_type:授权码模式固定为code。\
-
client_id:客户端id,和授权配置类中设置的 withClient 一致。\
-
redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。 \
-
scope:客户端范围,和授权配置类中设置的scopes一致(可选)\
-
state:防止csrf攻击(可选)\
\
输入用户名和密码
点击确认后: 选择是否接受资源授权?
\
选择接受:跳转到回调地址中,并且拿到授权码 ,框架自带了生成授权码的流程
六、申请令牌
1、使用postman发送post请求
(1)配置Basic Auth
(2)点击上图的“Preview Request”,在Headers中显示
(3)body中选择form-data,录入需要传递的参数,点击“send”,得到授权码
注意:不要反复使用一个code测试请求,否则会出现 invalid_token 错误
-
access_token:表示访问令牌,必选项。\
-
token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。\
-
expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。\
-
refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。\
-
scope:表示权限范围,如果与客户端申请的范围一致,可选项。\
\
七、生成JWT令牌
授权服务器中生成令牌:authcode_as
1、秘钥库文件
将 guli.keystore 放在resources目录下
2、配置文件中添加配置
encrypt.key-store.location=classpath:/guli.keystore
encrypt.key-store.secret=guli123store
encrypt.key-store.alias=gulikey
encrypt.key-store.password=guli123
3、用户对象转换器
将用户信息转换为map
package com.atguigu.authcodeas.config;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Override
public Map<String, Object> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new HashMap();
Object principal = authentication.getPrincipal();
UserDetails userDetails = (UserDetails) principal;
response.put("name", userDetails.getUsername());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
4、修改认证服务器配置文件
package com.atguigu.authcodeas.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.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.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 org.springframework.security.provisioning.InMemoryUserDetailsManager;
import javax.annotation.Resource;
import java.security.KeyPair;
/**
* 授权服务器配置
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
AuthorizationServerConfigurerAdapter {
// 添加商户信息
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()//token等信息存入内存
.withClient("clientapp")//客户端id
.secret(passwordEncoder().encode("123456"))//客户端secret
.redirectUris("http://localhost:8082/callback")//回调客户应用服务器
// 授权码模式
.authorizedGrantTypes("authorization_code")
.scopes("read_userinfo", "read_contacts");//细分权限
}
// 设置添加用户信息,正常应该从数据库中读取
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456"))
.authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567"))
.authorities("ROLE_USER").build());
return userDetailsService;
}
//注入用户信息
@Autowired
UserDetailsService userDetailsService;
@Bean //password加密的方式 相当于把PasswordEncoder类对象 注册到容器中
PasswordEncoder passwordEncoder() {
// 加密方式
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
//读取密钥的配置
@Bean("keyProp")
public KeyProperties keyProperties(){
return new KeyProperties();
}
//注入密钥的配置
@Resource(name = "keyProp")
private KeyProperties keyProperties;
//创建jwt令牌转换器
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//密钥工厂
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
keyProperties.getKeyStore().getLocation(),
keyProperties.getKeyStore().getSecret().toCharArray());
//密钥对(公钥和私钥)
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(
keyProperties.getKeyStore().getAlias(),
keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
//配置自定义的CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
//注入jwt令牌转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//创建tokenStore
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
//注入tokenStore
@Autowired
TokenStore tokenStore;
@Autowired
AuthenticationManager authenticationManager;
//授权服务器端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore)//令牌存储
.userDetailsService(userDetailsService);//用户信息service
}
}
\
八、使用令牌访问资源
资源服务器中校验令牌:authcode_rs\
1、公钥文件
将 publickkey.pem 放在resources目录下
2、修改资源服务器配置文件
package com.atguigu.authcoders.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
/**
* 资源服务配置
*/
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "publickey.pem";
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
//定义JwtAccessTokenConverter,使用jwt令牌
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
//定义JwtTokenStore,使用jwt令牌
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Override
public void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
http.authorizeRequests()
.anyRequest()//所有请求
.authenticated()//都要进行OAuth2验证
.and() //
.requestMatchers() //缩小匹配范围:
.antMatchers("/api/**");//对/api/*资源进行OAuth2验证
}
}
3、新建测试接口
因为使用的并不是用户名密码的方式授权,所以/api/userinfo接口并不能获取出用户信息
新建一个获取课程信息的接口
@RequestMapping("/api/couseIfo")
public ResponseEntity<Map<String, Object>> getCourseInfo() {
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("title", "Spring Security OAuth2.0");
map.put("price", 99);
return ResponseEntity.ok(map);
}