欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

某安全单位官网跳转至色情网站?微服务安全不容忽视!(I)

最编程 2024-06-28 22:49:03
...

前序

应用安全,不容忽视!!

 

 

今日开讲????

先来说说单体系统的安全

系统中某些页面只有在正常登录后才可以使用,用户请求这些页面时要检查session中有无该用户信息
解决方案:编写一个用于检测用户是否登录的过滤器,如果用户未登录,则重定向到指定的登录页面

再来说说集群环境下如何解决登陆问题

解决方案一:NG的iphash算法
IP绑定 ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
配置如下:
1 upstream backserver{
2   ip_hash;
3   server 192.168.0.1:80;
4   server 192.168.0.2:81;
5 }
View Code
缺点:不能充分考虑到各个服务器的性能,有可能同一时刻访问通过ip_hash出来都请求到一台服务器上,而且这台服务器的性能不行???

解决方案二:集中式Session(redis)。

核心工具类:

 1 public class CookieUtil {
 2   //domain    可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com” 
 3   //结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
 4   private static final String COOKIE_DOMAIN = ".zhangjiang.com";
 5   //name    Cookie的名称,Cookie一旦创建,名称便不可更改
 6   private static final String COOKIE_NAME = "zhangjiang_login_token";
 7 
 8   public static String readLoginToken(HttpServletRequest request) {
 9     Cookie[] cookies = request.getCookies();
10     if (cookies != null) {
11       for (Cookie cookie : cookies) {
12         log.info("read cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue());
13         if (StringUtils.equals(COOKIE_NAME, cookie.getName())) {
14            log.info("return cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue());
15           return cookie.getValue();
16         }
17       }
18     }
19     return null;
20    }
21 public static void delLoginToken(HttpServletRequest request, HttpServletResponse res ponse) { 
22   Cookie[] cookies = request.getCookies(); 
23   if (cookies != null) { 
24     for (Cookie cookie : cookies) { 
25       if (StringUtils.equals(COOKIE_NAME, cookie.getName())) { 
26          cookie.setDomain(COOKIE_DOMAIN); 
27          //path    Cookie的使用路径。如果设置为“/sessionWeb/”,则只有 
28          //contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本 
29          //域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
30          cookie.setPath("/"); 
31          // maxAge 设置为 0,表示将其删除 
32          cookie.setMaxAge(0); 
33         log.info("del cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue()); 
34         response.addCookie(cookie); 
35         return;
36         } 
37       } 
38      } 
39    }
40  } 
41 public static void writeLoginToken(HttpServletResponse response, String token) { 
42  Cookie cookie = new Cookie(COOKIE_NAME, token);
43  cookie.setDomain(COOKIE_DOMAIN); 
44  cookie.setPath("/"); 
45  // 防止脚本攻击 
46  cookie.setHttpOnly(true); 
47  // 单位是秒,如果是 ‐1,代表永久; 
48  // 如果 MaxAge 不设置,cookie 不会写入硬盘,而是在内存,只在当前页面有效 
49  cookie.setMaxAge(60 * 60 * 24 * 365); 
50  log.info("write cookieName:{}, cookieValue:{}", cookie.getName(), 
51  cookie.getValue()); 
52  response.addCookie(cookie);
53  }
54  ======登陆成功==== 
55  CookieUtil.writeLoginToken(response, session.getId()); 
56 自定义的RedisUtil.setEx(session.getId(), 自定义的JsonUtil.obj2Str(serverResponse.getData()), 超时时间);
57 =======退出登陆======== 
58  String loginToken = CookieUtil.readLoginToken(request); 
59  CookieUtil.delLoginToken(request, response);
60  自定义的RedisUtil.del(loginToken);
61  ========获取用户信息======= 
62  String loginToken = CookieUtil.readLoginToken(request); 
63  if (StringUtils.isEmpty(loginToken)) {
64  return "用户未登录,无法获取当前用户信息";
65  } 
66 String userJsonStr = 自定义的RedisUtil.get(loginToken);
67  User user = 自定义的JsonUtil.str2Obj(userJsonStr, User.class); 
68 
69  SessionExpireFilter 过滤器 
70  另外,在用户登录后,每次操作后,都需要重置 Session 的有效期。可以使用过滤器来实现 
71 public class SessionExpireFilter implements Filter { 
72 
73 @Override 
74  public void init(自定义配置类FilterConfig filterConfig) throws ServletException { } 
75 
76  @Override 
77  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 
78  HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; 
79  String loginToken = CookieUtil.readLoginToken(httpServletRequest); 
80  if (StringUtils.isNotEmpty(loginToken)) { 
81    String userJsonStr = 自定义的RedisUtil.get(loginToken); 
82    User user = 自定义的JsonUtil.str2Obj(userJsonStr, User.class); 
83    if (user != null) { 
84       自定义的RedisUtil.expire(loginToken, 超时时间); 
85    } 
86  } 
87  filterChain.doFilter(servletRequest, servletResponse); 
88  } 
89 
90  @Override 
91  public void destroy() { } 
92  }   
View Code

 

微服务安全

1)什么是一个Oauth2协议?

你真的没有接触过Oatuh2协议么?不,你肯定接触过。
生活场景:你玩 王者农药登陆账号的时候,有一个用QQ登陆的选项,然后你点击该按钮,然后通过QQ登陆,进入游戏。

 

OAuth(开放授权)是一个开放标准,允许用户(你)授权第三方应用(王者农药)访问用户存储在另外的服务提供者(QQ服务器)上的信息,而不需要将用户名和密码提供给第三方移动应用(王者农药)­­­­­这个就是典型的授权码模式
 
再比如密码模式 (使用场景 :第三方APP是高度被信任的)     张三,通过浏览器访问  "云冲印网站",去打印你在微信上存储的照片。
问题是只有得到用户张三的授权,微信才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢?传统方法是,用户将自己的微信用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。
这样的做法有以下几个严重的缺点。
缺点:
(1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。
(2)"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。
(3)用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。
(4)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏
 
名称解释:
(1) Third-party application:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。
(2)HTTP service:                 HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的微信服务器
(3)Resource Owner:           资源所有者,本文中又称"用户"(user)。你
(4)User Agent:                     用户代理,本文中就是指浏览器。
(5)Authorization server:     认证服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:            资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
 

2)Oauth2的四种授权模式

a)密码模式 适用的业务场景 客户端应用(手机app) 是高度受信用的,一般是自己公司开发的app项目。
b)授权码模式(最安全的模式) 业务场景 第三方不授信的,搭建自己的开发能力平台。

 

1.:获取授权码http://localhost:8080/oauth/authorize?response_type=code&client_id=portal_app&redirect_uri=http://www.baidu.com&state=abc
参数说明:
client_id             认证服务器分配给第三方客户端的appid
response_type: 固定格式 值位code
redirect_uri:       用户在认证服务器上登陆成功了 需要回调到 客户端应用上
state:                  你传什么给授权服务器 授权服务器原封不动的返回给你
2:获取到code,去换取token
http://localhost:8080/oauth/token(Post请求)

C)简化模式(开发中几乎接触不到 适用于 客户端就是一堆js css html 没有前端服务器) 

d):客户端模式(开发中几乎用不到,这种模式 用户都没有参与过程)

动手搭建微服务认证中心实现微服务鉴权

角色:
认证中心(认证服务器)
订单,支付,库存等后端微服务(资源)
前端工程 web-portal工程
你. 
 

 

动手搭建认证中心
1)创建工程名称为zhangjiang-auth-server,加入依赖
主要是添加spring-cloud-starter-oauth2 
 1 <dependencies>
 2     <dependency> 
 3          <groupId>org.springframework.boot</groupId> 
 4          <artifactId>spring‐boot‐starter</artifactId> 
 5      </dependency>
 6 
 7     <dependency> 
 8          <groupId>org.springframework.boot</groupId> 
 9          <artifactId>spring‐boot‐starter‐web</artifactId> 
10      </dependency>
11 
12 <!‐‐这里引入微服务注册中心nacos的包‐‐> 
13     <dependency> 
14      <groupId>com.alibaba.cloud</groupId> 
15      <artifactId>spring‐cloud‐alibaba‐nacos‐discovery</artifactId>
16      </dependency> 
17 
18  <!‐‐Oauth2的包‐‐> 
19      <dependency> 
20      <groupId>org.springframework.cloud</groupId> 
21      <artifactId>spring‐cloud‐starter‐oauth2</artifactId> 
22      </dependency> 
23 
24  </dependencies>                
View Code
2)添加注解写配置文件
2.1)作为认证服务器,那么就会又认证服务器的配置
写一个配置类 ZhangjiangAuthorizationServerConfig 实现AuthorizationServerConfigurerAdapter
 
添加@EnableAuthorizationServer
AuthorizationServerConfigurerAdapter 类有三个配置方法,我们都需要覆盖
 
①:第三方客户端配置,配置哪些应用可以来访问我们认证服务器。
他有二种存储模式,一种是内存模式,一种是db模式(用于生产),后面讲
下面的配置解释说明:
为第三方客户端 分配一个client_id为 portal_app,密码为portal_app,他的权限访问是read权限的我服务器颁发的token 有效期是1一个小时, 我拿着这个token令牌可以访问order-service,product-
service二个微服务。
order-server和product-server同理,也是分配了账号。 
 
配置类代码如下:
 1 @Override
 2 public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
 3   /**
 4   * 配置解析 授权服务器指定客户端(第三方应用)能访问授权服务器
 5    * 为第三方应用颁发客户端 id为 ,密码zhangjiang
 6   * 支持的授权类型为 密码模式(有四种模式,后面说)
 7   * 颁发的令牌的有效期为1小时
 8   * 通过该令牌可以访问 哪些资源服务器(order‐service) 可以配置多个
 9   * 访问资源服务器的read write权限
10   */
11   clients.inMemory().withClient("portal_app")
12   .secret(passwordEncoder.encode("portal_app"))
13   .authorizedGrantTypes("password")
14   .scopes("read").accessTokenValiditySeconds(3600)
15   .resourceIds("order‐service","product‐service").and()
16   .withClient("order_app").secret(passwordEncoder.encode("zhangjiang"))
17   .accessTokenValiditySeconds(1800).scopes("read")
18   .authorizedGrantTypes("password").resourceIds("order‐service").and()
19   .withClient("product_app").secret(passwordEncoder.encode("zhangjiang"))
20   .accessTokenValiditySeconds(1800).scopes("read")
21   .authorizedGrantTypes("password") .resourceIds("product‐service");
22 }
23   
24   
View Code
②:针对用户的配置,也就是说,第三方客户端带入过来的用户名,密码我认证中心怎么去验证他的正确性?
那么authenticationManager是一个什么东西? 学名(认证管理器,用来给用户认证的),那么authenticationManager 怎么来的??这里是一个疑问,请接下来再看 
1 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
2   endpoints.authenticationManager(authenticationManager);
3 }
View Code
③:针对 资源服务器 来校验令牌的配置。
意思,你资源服务器来校验令牌 需要带入client_id和client_securet过来 
1 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
2   //获取tokenKey需要登录
3   security.checkTokenAccess("isAuthenticated()");
4 }
View Code
2.2)认证服务器的安全配置
①:写一个配置类WebSecurityConfig,实现 WebSecurityConfigurerAdapter类,有一个配置方法,这个方法就是构建我们的authenticationManager对象,而构建该对象需要配置传递一个userDetailsService,以及一个加密器对象passwordEncoder 
1 @Override
2 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
3  auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
4 }
View Code
问题?userDetailsService怎么来? passwordEncoder怎么来?
配置加密器对象
1 @Bean
2 public PasswordEncoder passwordEncoder() {
3   return new BCryptPasswordEncoder();
4 }
View Code
写一个UserDetailService实现 UserDetailsService接口用于用户登陆认证的。 
 1 @Component("userDetailsService")
 2 @Slf4j
 3 public class  ZhangjiangUserDetailService implements UserDetailsService {
 4   
 5   @Autowired
 6   private PasswordEncoder passwordEncoder;
 7   
 8   @Override
 9   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundExcept ion {
10   //需要登录数据库,这里示例先写死
11   log.info("当前登陆用户名为:{}",username);
12 
13   return User.builder().username(username)
14   .password(passwordEncoder.encode("123456")).authorities("ROLE_ADMIN")
15   .build();
16   }
17 }
View Code
现在有了UserDetailsService 和passwordEncoder对象了 现在可以创建AuthenticationManager
1 @Bean
2  public AuthenticationManager authenticationManagerBean() throws Exception {
3    return super.authenticationManagerBean();
4  }
View Code
到这里就是配置好了我们的认证服务器了。测试认证中心。
测试地址:http://localhost:8080/oauth/token
postman请求时header里要add authorization,以表单形式提交
测试参数:请求头参数
名字 文本类型 备注
username text zhangjiang 在认证服务器上的账户
password text 123456 在认证服务器上的密码
grant_type text password 密码模式
scope text read 令牌的权限,这里是读
返回access_token和expire即为成功