某安全单位官网跳转至色情网站?微服务安全不容忽视!(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 }
缺点:不能充分考虑到各个服务器的性能,有可能同一时刻访问通过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 }
微服务安全
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>
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
②:针对用户的配置,也就是说,第三方客户端带入过来的用户名,密码我认证中心怎么去验证他的正确性?
那么authenticationManager是一个什么东西? 学名(认证管理器,用来给用户认证的),那么authenticationManager 怎么来的??这里是一个疑问,请接下来再看
1 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 2 endpoints.authenticationManager(authenticationManager); 3 }
③:针对 资源服务器 来校验令牌的配置。
意思,你资源服务器来校验令牌 需要带入client_id和client_securet过来
1 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 2 //获取tokenKey需要登录 3 security.checkTokenAccess("isAuthenticated()"); 4 }
2.2)认证服务器的安全配置
①:写一个配置类WebSecurityConfig,实现 WebSecurityConfigurerAdapter类,有一个配置方法,这个方法就是构建我们的authenticationManager对象,而构建该对象需要配置传递一个userDetailsService,以及一个加密器对象passwordEncoder
1 @Override 2 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 3 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 4 }
问题?userDetailsService怎么来? passwordEncoder怎么来?
配置加密器对象
1 @Bean 2 public PasswordEncoder passwordEncoder() { 3 return new BCryptPasswordEncoder(); 4 }
写一个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 }
现在有了UserDetailsService 和passwordEncoder对象了 现在可以创建AuthenticationManager
1 @Bean 2 public AuthenticationManager authenticationManagerBean() throws Exception { 3 return super.authenticationManagerBean(); 4 }
到这里就是配置好了我们的认证服务器了。测试认证中心。
测试地址: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即为成功
上一篇: PHP 子类继承父类的构造方法