这里说点废话 , 在Spring Cloud 中我们讲模块拆分成各个小的服务,各个服务之间通过远程请求进行调用,这样得架构我们称之为微服务。在单体架构中,用户得认证得和授权都是在一个服务中进行。
然后我们说说微服务中我们是怎么处理微服务的登录,权限,认证。
然后说说Spring Cloud 中得一些概念 这是重点啊 ,重点 ,重点。首先说一下我们是用OAuth2 协议来做用户认证得 ,Spring Cloud 中集成了这个模块
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
这里面集成好了Spring Security 模块,所以不需要我们再次对Secuirty进行集成。
AuthorizationServer
这里我们先说下流程 , 就是我们平时使用得用户进入某宝然后进行选择登录,登录选项有很多,比如说支付宝登录 , 那么它呢请求得就是支付宝得 OAuth2
接口进行得认证,然后呢在某宝自己服务这里根据OAuth2
协议的回调结果再进行处理,从而主动对该用户进行认证通过并赋予哪些角色,也就是拥有哪些权限。
同样这样得架构在我们这里也可以进行实现。我们使用得就是Spring Security OAuth2
进行实现得。首先有一个授权服务
,这里说明下授权,仅仅是授权,也就是负责签发token,不对用户得权限进行限制。授权成功后授权服务器回生成一个 code
,然后回调进入我们注册好得回调地址,并且将code
携带进入,然后这时候我们这个服务(注意这里是别的服务了,属于资源服务了,不是认证服务,认证服务回调进入资源服务), 然后我们资源服务利用这个code
,再去请求认证服务,来获取access_token
,当我们获取到这个token得时候,我们将token信息加入到response header
中,这样前端每次请求中都要携带这个token才可以访问资源。(授权码模式)接下来访问资源得时候,我们还要对用户访问得资源进行校验,看是否符合权限,因此我们还需要个权限服务,用户来获取用户都有哪些权限得服务。
当然这只是一种方案 ,还有其他方案,比如说我们只在网关处校验权限。
WebSecurityConfig
首先说这个类,这个类是属于Spring Security的 ,它使用最上面的过滤链,主要是配置哪些接口可以访问不可以访问,权限就是在这里进行限制的。
@Order(1)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService baseUserDetailsService;
@Autowired
private BlogUserMapper blogUserMapper;
@Value("${security.remember.time}")
private Integer rememberTime;
@Value("${security.oauth2.secret}")
private String secret;
/*
配置TokenRepository
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new Pbkdf2PasswordEncoder(secret);
}
@Bean
@Primary
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* @Author myk
* @Description //用户验证
* @Date 10:05 2019/12/24
* @Param [auth]
* @return void
**/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(baseUserDetailsService).passwordEncoder(passwordEncoder());
}
/**
* 允许匿名访问所有接口 主要是 oauth 接口
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/security/login","/oauth/**","/actuator/**").permitAll()
.antMatchers("/js/**","/html/**","/img/**","/font/**","/css/**","/layer/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().loginPage("/html/login.html")
.loginProcessingUrl("/security/login")
.and()
.logout().logoutUrl("/security/logout")
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
//两个星期
.tokenValiditySeconds(rememberTime);
}
AuthorizationServerConfig
这个类是用来Security 集成OAuth2的,注意这里使用了@EnableAuthorizationServer
注解,配置为一个认证中心
这里使用的数据存储的客户端信息,一会来解释数据库中字段的参数是用来做什么的,并且这里配置了jwt增强类,将我们的用户信息写入了jwt中,当然密码不能写进来。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
public UserDetailsService baseUserDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore jwtTokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private DataSource dataSource;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Bean("jdbcClientDetailsService")
public JdbcClientDetailsService getJdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单验证
security.allowFormAuthenticationForClients()
//开启 /oauth/token_key 验证端口无权限访问
.tokenKeyAccess("permitAll()")
// 开启 /oauth/check_token 验证端口认证权限访问
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用JdbcClientDetailsService客户端详情服务
clients.withClientDetails(getJdbcClientDetailsService());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 普通jwt 模式 支持password 模式
endpoints.authenticationManager(authenticationManager).allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST,HttpMethod.OPTIONS)
// .userDetailsService(baseUserDetailsService)
.tokenStore(jwtTokenStore);
endpoints.tokenServices(defaultTokenServices());
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET,HttpMethod.OPTIONS);
}
/**
* 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
* @return
*/
@Primary
@Bean
public DefaultTokenServices defaultTokenServices(){
//添加用户信息
List<TokenEnhancer> tokenEnhancerList = new ArrayList<>();
tokenEnhancerList.add(jwtTokenEnhancer);
tokenEnhancerList.add(jwtAccessTokenConverter);
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(tokenEnhancerList);
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(getJdbcClientDetailsService());
tokenServices.setAccessTokenValiditySeconds(60*60*12); // token有效期自定义设置,默认12小时
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默认30天,这里修改
tokenServices.setTokenEnhancer(enhancerChain);
return tokenServices;
}
}
JwtTokenConfig
@Configuration
public class JwtTokenConfig {
@Bean
@Primary
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* @Author myk
* @Description 转换器
* @Date 16:19 2019/12/17
* @Param []
* @return org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
**/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtTokenEnhancer();
// 导入证书,这里我们使用jdk加密token
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("ailuoli.jks"), "Ws961226".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("ailuoli"));
return converter;
}
}
JwtTokenEnhancer
jwt 增强类
@Component
public class JwtTokenEnhancer extends JwtAccessTokenConverter {
/**
* @Author myk
* @Description //将用户信息写入token
* @Date 16:24 2019/12/17
* @Param [oAuth2AccessToken, oAuth2Authentication]
* @return org.springframework.security.oauth2.common.OAuth2AccessToken
**/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(oAuth2AccessToken);
BaseSecurityUser blogUser = (BaseSecurityUser) oAuth2Authentication.getPrincipal();
blogUser.setCredential(null);
defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO,blogUser);
return super.enhance(defaultOAuth2AccessToken,oAuth2Authentication);
}
/**
* 解析token
* @param value
* @param map
* @return
*/
@Override
public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map){
OAuth2AccessToken oauth2AccessToken = super.extractAccessToken(value, map);
convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation());
return oauth2AccessToken;
}
private void convertData(OAuth2AccessToken accessToken, Map<String, ?> map) {
accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO)));
}
private BlogUser convertUserData(Object map) {
String json = JsonUtils.deserializer(map);
return JsonUtils.serializable(json, BlogUser.class);
}
}
-- ----------------------------
-- Table structure for ClientDetails
-- ----------------------------
DROP TABLE IF EXISTS `ClientDetails`;
CREATE TABLE `ClientDetails` (
`appId` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`resourceIds` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`appSecret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`grantTypes` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`redirectUrl` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additionalInformation` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`autoApproveScopes` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`appId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`authentication` blob NULL,
`refresh_token` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`clientId` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`expiresAt` timestamp(0) NULL DEFAULT NULL,
`lastModifiedAt` timestamp(0) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`resource_ids` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`client_secret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`autoapprove` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token` (
`token_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
这里其中最关键的表就是oauth_client_details
client_id
首先说的就是这个,因为刚才搭建的是server,所以再它看来,其他要它进行认证的服务都是client,这个是client的标识,不能重复,相当于用户的用户名resource_ids
是客户端拥有访问资源的id集合client_secret
这是相当于用户的密码,只不过它是用来认证客户端的,需要加密scope
这个字段是授权访问的作用域 all, select,read,write 等authorized_grant_types
这个是Oauth2 认证的类型,我们本文描述的是授权码类型,参数有 authorization_code,refresh_token
web_server_redirect_uri
这个是回调到客户端的地址比如http://127.0.0.1:9001/user/service/oauth/callback
access_token_validity
access_token 的有效期refresh_token_validity
refresh_token_validity的有效期autoapprove
是否需要用户手动授权,false 就需要手动点击授权如果会一些spring security的知识 这里我们应该可以看得懂 , 授权服务器搭建就到这里
JwtTokenConfig
同样是jwt配置类 ,这里是对jwt进行得解析,需要jdk加密得公钥
@Configuration
public class JwtTokenConfig {
@Bean
@Primary
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* @Author myk
* @Description 转换器
* @Date 16:19 2019/12/17
* @Param []
* @return org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
**/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
//用作 JWT 转换器
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); //设置公钥
return converter;
}
}
JwtTokenEnhancer
这个类和认证服务器得一样
ResourceConfiguration
这个类标记了 @EnableResourceServer
注解 表明它是个资源服务器们也就是一个客户端(client)
@Configuration
@EnableResourceServer
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore jwtTokenStore;
@Value("${security.oauth2.secret}")
private String secret;
@Bean
public PasswordEncoder passwordEncoder() {
return new Pbkdf2PasswordEncoder(secret);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(jwtTokenStore);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors()
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/blog/user_info","/oauth/**","/actuator/**","/sys/user/register").permitAll()
.anyRequest().authenticated();
}
}
OauthCallBackController
重点来了 ,这个回调地址,我们在数据库中配置得地址,会携带code进入这个类,然后这个类中再去使用rest请求token。注意类型是MediaType.MULTIPART_FORM_DATA
,不是json
package com.cloud.blog.user.controller;
import com.cloud.blog.common.config.exception.BlogResponse;
import com.cloud.blog.user.config.pojo.JWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
/**
* @ClassName BlogCallBackController
* @Description 接受认证成功的回调地址
* @Author wsail
* @Date 2019/12/27 15:16
**/
@RestController
public class OauthCallBackController {
@Value("${security.oauth2.client.access-token-uri}")
private String oauth_token;
@Value("${security.oauth2.client.registered-redirect-uri}")
private String redirect_uri;
@Value("${security.oauth2.client.authorized-grant-types}")
private String grant_type;
@Value("${security.oauth2.client.client-id}")
private String client_id;
@Value("${security.oauth2.client.client-secret}")
private String client_secret;
private final static String CONTENT_TYPE = "Content-Type";
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/oauth/callback",produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public BlogResponse<JWT> authorizationSuccessCallBack(String code, HttpServletRequest request){
System.out.println(request.getMethod());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String,String> map = new LinkedMultiValueMap<>();
map.add("client_id",client_id);
map.add("client_secret",client_secret);
map.add("code",code);
map.add("redirect_uri",redirect_uri);
map.add("grant_type",grant_type);
httpHeaders.add(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(map,httpHeaders);
ResponseEntity<JWT> responseEntity = restTemplate.postForEntity(oauth_token,httpEntity,JWT.class);
JWT jwt = responseEntity.getBody();
return new BlogResponse<>(jwt);
}
}
application.yml
资源服务得配置,有坑! 有坑 !有坑!
注意这里得resource token-info-uri
一定要写 ,一定要写 ,一定要写对 ,不让使用token请求数据得时候会抛出异常
security:
oauth2:
client:
client-id: user-service
client-secret: Ws961226
user-authorization-uri: http://127.0.0.1:9527/auth/server/oauth/authorize
access-token-uri: http://127.0.0.1:9527/auth/server/oauth/token
registered-redirect-uri: http://127.0.0.1:9001/user/service/oauth/callback
authorized-grant-types: authorization_code
resource:
token-info-uri: http://127.0.0.1:9527/auth/server/oauth/check_token # 检查 token 是否有效
authorization:
check-token-access: http://127.0.0.1:9527/auth/server/oauth/check_token
到这里我们得资源服务器也搭建完成 ,上成果
访问进行登录127.0.0.1:9527/auth/server/oauth/authorize?response_type=code&client_id=user-service&redirect_uri=http://127.0.0.1:9001/user/service/oauth/callback
成功被认证服务security进行拦截,并跳转到了登录界面 ,我们登录
成功将jwt信息进行了返回,然后我们先请求一个资源服务得接口,不添加token得情况
显然是被Oauth 进行了拦截,然后我们添加token试试
成功!至此OAuth2集成就完成了 ,接下来就是对用户权限得进行限制,我们还需要搭建权限服务,这里就不赘述了,有兴趣得小伙伴可以自己试试。[email protected] 有问题可以问我
git源码在下面git源码