springcloud 相同服务名_SpringCloud微服务间安全调用实现

如果对你有用,请记得点赞哦!!!!

SpringCloud服务间安全调用实现

目前项目中用到了微服务认证这块的技术,查看了相当一大部分的资料发现现在网上的资料都很不全,很零散。而且很多都运行不起来。

简单的介绍一下认证。

传统的项目都是使用session来管理用户的登录信息,返回前端sessionId保存在cookie中。但是在分布式情况下利用session去管理的场景就很少了,也不是不可以,如果你对session很热爱使用它的话,那就没必要继续往下看了。

在我们微服务restful风格下,应该怎么去保证安全信息呢。相信大家可能了解过token,也就是我们熟说的令牌,有人问我什么是token,Token 和 Session ID 不同,并非只是一个 key。Token 一般会包含用户的相关信息,通过验证 Token 就可以完成身份校验。

这里我们先简单介绍一下我们使用的token格式。

采用jwt(JSON WEB TOKEN)

JWT 是由三段信息构成的,第一段为头部(Header),第二段为载荷(Payload),第三段为签名(Signature)。每一段内容都是一个 JSON 对象,将每一段 JSON 对象采用 BASE64 编码,将编码后的内容用. 链接一起就构成了 JWT 字符串。

认证协议采用Oauth2.0

OAuth 是一种开放的协议,为桌面程序或者基于 BS 的 web 应用提供了一种简单的,标准的方式去访问需要用户授权的 API 服务。OAUTH 认证授权具有以下特点:

简单:不管是 OAuth 服务提供者还是应用开发者,都很容易于理解与使用;

安全:没有涉及到用户密钥等信息,更安全更灵活;

开放:任何服务提供商都可以实现 OAuth,任何软件开发商都可以使用 OAuth;

关于jwt和Oauth我们就不做过多的介绍了。大家直接去百度一大堆,这种内容写出来就没有技术含量了。

思考一个问题:就是在分布式情况下,我们怎么去生成这个token?在哪里生成是最好的呢?

现在我们举例说明,有三个服务,会员,订单,支付,这个三个服务,他们都需要用户验证,我们不可能在每个系统中去写一个认证过程,所以,我们的认证是一个单独的服务。

认证流程:用户提供用户信息,到认证中心验证,成功就返回一个token,访问其他服务的时候在请求头携带上token信息,服务只需要去解析token的值就可以了,这样做既满足了微服务轻量级的需求,也避免了了浏览器禁用cookie的情况,岂不美哉。

我们要知道一个事情,对于我们的认证服务器来说,只有两种服务,那就是认证服务器和资源服务器,所有我们这里的会员,订单,支付服务,对于认证服务器来说都是资源服务器。

搭建认证服务

我们采用SpringSecurity Oauth2.0 JWT Redis搭建服务

采用SpringSecurity的密码模式,其他模式请自行百度参考,这里提供当前认证的最需求的做法。

pom.xml

org.springframework.boot

spring-boot-starter-parent

1.5.2.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

org.springframework.security.oauth

spring-security-oauth2

org.springframework.security

spring-security-jwt

com.alibaba

fastjson

1.2.40

org.springframework.cloud

spring-cloud-dependencies

Dalston.RC1

pom

import

org.springframework.boot

spring-boot-maven-plugin

com.cdhenren.AuthApplication

spring-milestones

Spring Milestones

https://repo.spring.io/milestone

false

配置文件application.yml

server:

port: 8080

spring:

redis:

host: 127.0.0.1

password: null

port: 6379

pool:

max-idle: 100

min-idle: 1

max-active: 1000

max-wait: -1

logging:

level:

per.lx: DEBUG

配置认证服务

@Configuration

@EnableAuthorizationServer

public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

// 资源ID

private static final String SOURCE_ID = "order";

private static final int ACCESS_TOKEN_TIMER = 60 * 60 * 24;

private static final int REFRESH_TOKEN_TIMER = 60 * 60 * 24 * 30;

@Autowired

AuthenticationManager authenticationManager;

@Autowired

RedisConnectionFactory redisConnectionFactory;

@Override

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

clients.inMemory().withClient("myapp").resourceIds(SOURCE_ID).authorizedGrantTypes("password", "refresh_token")

.scopes("all").authorities("ADMIN").secret("lxapp").accessTokenValiditySeconds(ACCESS_TOKEN_TIMER)

.refreshTokenValiditySeconds(REFRESH_TOKEN_TIMER);

}

@Override

public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

endpoints.accessTokenConverter(accessTokenConverter());

endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);

}

@Override

public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {

// 允许表单认证

oauthServer.allowFormAuthenticationForClients();

}

// JWT

@Bean

public JwtAccessTokenConverter accessTokenConverter() {

JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {

/***

* 重写增强token方法,用于自定义一些token总需要封装的信息

*/

@Override

public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {

String userName = authentication.getUserAuthentication().getName();

// 得到用户名,去处理数据库可以拿到当前用户的信息和角色信息(需要传递到服务中用到的信息)

final Map additionalInformation = new HashMap<>();

// Map假装用户实体

Map userinfo = new HashMap<>();

userinfo.put("id", "1");

userinfo.put("username", "LiaoXiang");

userinfo.put("qqnum", "438944209");

userinfo.put("userFlag", "1");

additionalInformation.put("userinfo", JSON.toJSONString(userinfo));

((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);

OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);

return enhancedToken;

}

};

// 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式

accessTokenConverter.setSigningKey("SigningKey");

return accessTokenConverter;

}

@Bean

public TokenStore tokenStore() {

RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);

return tokenStore;

}

}

安全认证配置

@Configuration

@EnableWebSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

// 请配置这个,以保证在刷新Token时能成功刷新

@Autowired

public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {

// 配置用户来源于数据库

auth.userDetailsService(userDetailsService());

}

@Bean

@Override

protected UserDetailsService userDetailsService() {

// 这里是添加两个用户到内存中去,实际中是从#下面去通过数据库判断用户是否存在

InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());

manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());

return manager;

// #####################实际开发中在下面写从数据库获取数据###############################

// return new UserDetailsService() {

// @Override

// public UserDetails loadUserByUsername(String username) throws

// UsernameNotFoundException {

// // 通过用户名获取用户信息

// boolean isUserExist = false;

// if (isUserExist) {

// //创建spring security安全用户和对应的权限(从数据库查找)

// User user = new User("username", "password",

// AuthorityUtils.createAuthorityList("admin", "manager"));

// return user;

// } else {

// throw new UsernameNotFoundException("用户[" + username + "]不存在");

// }

// }

// };

}

@Override

protected void configure(HttpSecurity http) throws Exception {

// @formatter:off

http.requestMatchers().anyRequest().and().authorizeRequests().antMatchers("/oauth/**").permitAll();

// @formatter:on

}

}

到这里一个认证服务器就搭建成功了。

我们写一个启动类来启动

@SpringBootApplication

public class AuthApp {

public static void main(String[] args) {

SpringApplication.run(AuthApp.class, args);

}

}

启动后发现控制台输出了我们的请求连接(表明集成成功)

然后我们用postman进行请求模拟

请求参数中的信息来自于配置文件,不同的话不能生成成功。用户信息请自行修改代码从数据库获取

可以看到我们的返回信息中包含的信息,其中有固定的信息和我们自定义的信息。

顺便看看我们的Redis中存储的认证信息,可以看到redis新增了很多信息

到这里我们的认证服务就搭建成功了,就可以用来访问其他的服务了

#######################################################################

我们这里来搭建资源服务器,也就是通过TOKEN去访问的服务

pom.xml

org.springframework.boot

spring-boot-starter-parent

1.5.2.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

org.springframework.security.oauth

spring-security-oauth2

org.springframework.security

spring-security-jwt

commons-lang

commons-lang

2.6

com.alibaba

fastjson

1.2.40

io.jsonwebtoken

jjwt

0.7.0

org.springframework.cloud

spring-cloud-dependencies

Dalston.RC1

pom

import

org.springframework.boot

spring-boot-maven-plugin

com.cdhenren.AuthApplication

spring-milestones

Spring Milestones

https://repo.spring.io/milestone

false

新建一个资源服务配置文件ResourceConfiguration.java

我们这里配置以/order/*开头的请求不用认证

@Configuration

@EnableResourceServer

public class ResourceConfiguration extends ResourceServerConfigurerAdapter {

private static final String SOURCE_ID = "order";

@Autowired

private RedisConnectionFactory redisConnectionFactory;

@Override

@CrossOrigin

public void configure(ResourceServerSecurityConfigurer resources) {

resources.resourceId(SOURCE_ID).stateless(true);

resources.tokenServices(defaultTokenServices());

}

@Override

public void configure(HttpSecurity http) throws Exception {

// @formatter:off

// 我们这里放开/order/*的请求,以/order/*开头的请求不用认证

http.authorizeRequests().antMatchers("/order/*").permitAll().and().authorizeRequests()

.antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();

// @formatter:on

}

// 自定义的Token存储器,存到Redis中

@Bean

public TokenStore tokenStore() {

RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);

return tokenStore;

}

// Token转换器

@Bean

public JwtAccessTokenConverter accessTokenConverter() {

JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {

};

accessTokenConverter.setSigningKey("SigningKey");

return accessTokenConverter;

}

/**

* 创建一个默认的资源服务token

*/

@Bean

public ResourceServerTokenServices defaultTokenServices() {

final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();

// 使用自定义的Token转换器

defaultTokenServices.setTokenEnhancer(accessTokenConverter());

// 使用自定义的tokenStore

defaultTokenServices.setTokenStore(tokenStore());

return defaultTokenServices;

}

}

然后我们写一个工具类,去获取我们在认证端传过来的userinfo

public class AuthUtils {

public static String getReqUser(HttpServletRequest req) {

String header = req.getHeader("Authorization");

String token = StringUtils.substringAfter(header, "bearer");

Claims claims;

try {

claims = Jwts.parser().setSigningKey("SigningKey".getBytes("UTF-8")).parseClaimsJws(token).getBody();

} catch (Exception e) {

return null;

}

String localUser = (String) claims.get("userinfo");

// 拿到当前用户

return localUser;

}

}

到这里我们就配置完了我们的所有请求,我们编写一个Controller进行验证。

@RestController

public class TestEndpoints {

@GetMapping("/product/{id}")

public String getProduct(@PathVariable String id, HttpServletRequest req) {

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

System.out.println("用户名 : " + JSON.toJSONString(authentication.getPrincipal()));

System.out.println("封装的传递信息 : " + AuthUtils.getReqUser(req));

return "(Need Auth Request)product id : " + id;

}

@GetMapping("/order/{id}")

public String getOrder(@PathVariable String id) {

return "(No Auth Request)order id : " + id;

}

}

这下我们就测试完成了

首先访问我们不需要认证的请求:127.0.0.1:8081/order/1,可以正常访问返回数据

然后不带我们的token进行访问另外一个链接(需要认证):127.0.0.1:8081/product/1

可以看到返回没有认证不能访问的提示,如下图

下面我们在请求头里面带上我们的token,这里注意一下,token是携带在Header中的Authorization属性中,而且我们需要用token类型+token的方式进行传递,这里我们的类型默认是bearer,加上我们的请求头就可以正常访问到数据了。如下图

现在,我们可以直接拿去集成到我们真实的微服务项目中去了,保证我们的项目从这一篇博客开始。

如果对你有用,请记得点赞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值