接上篇文章,在这个流程中,PostMan可以代表客户端应用,订单服务是资源服务器,唯一缺少的是 认证服务器 ,下面来搭建认证服务器

项目结构:

Pom.xml : DependencyManager 引入SpringCloud的配置,Dependency引入  spring-cloud-starter-oauth2

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5.  
  6. <groupId>com.nb.security</groupId>
  7. <artifactId>nb-server-auth</artifactId>
  8. <version>0.0.1-SNAPSHOT</version>
  9.  
  10. <properties>
  11. <mybatis-plus.version>3.1.2</mybatis-plus.version>
  12. <java.version>1.8</java.version>
  13. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. <mybatis-plus.version>3.1.2</mybatis-plus.version>
  16. <druid.version>1.1.17</druid.version>
  17. <jwt.version>0.9.1</jwt.version>
  18. <commons.version>2.6</commons.version>
  19. <aliyun-java-sdk-core.version>3.2.3</aliyun-java-sdk-core.version>
  20. <aliyun-java-sdk-dysmsapi.version>1.0.0</aliyun-java-sdk-dysmsapi.version>
  21. <aliyun.oss.version>3.6.0</aliyun.oss.version>
  22. <qc.cos.version>5.6.5</qc.cos.version>
  23. </properties>
  24.  
  25. <dependencyManagement>
  26. <dependencies>
  27. <dependency>
  28. <!-- Import dependency management from Spring Boot -->
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-dependencies</artifactId>
  31. <version>2.1.6.RELEASE</version>
  32. <type>pom</type>
  33. <scope>import</scope>
  34. </dependency>
  35.  
  36. <!--spring cloud-->
  37. <dependency>
  38. <groupId>org.springframework.cloud</groupId>
  39. <artifactId>spring-cloud-dependencies</artifactId>
  40. <version>Greenwich.SR2</version>
  41. <type>pom</type>
  42. <scope>import</scope>
  43. </dependency>
  44.  
  45. </dependencies>
  46. </dependencyManagement>
  47.  
  48. <dependencies>
  49.  
  50. <dependency>
  51. <groupId>org.springframework.boot</groupId>
  52. <artifactId>spring-boot-starter-web</artifactId>
  53. </dependency>
  54.  
  55. <dependency>
  56. <groupId>org.springframework.cloud</groupId>
  57. <artifactId>spring-cloud-starter-oauth2</artifactId>
  58. </dependency>
  59. <!--集成mybatisplus-->
  60. <!-- mybatis-plus -->
  61. <dependency>
  62. <groupId>com.baomidou</groupId>
  63. <artifactId>mybatis-plus-boot-starter</artifactId>
  64. <version>${mybatis-plus.version}</version>
  65. </dependency>
  66. <dependency>
  67. <groupId>com.baomidou</groupId>
  68. <artifactId>mybatis-plus-generator</artifactId>
  69. <version>3.2.0</version>
  70. </dependency>
  71. <dependency>
  72. <groupId>org.apache.velocity</groupId>
  73. <artifactId>velocity-engine-core</artifactId>
  74. <version>2.1</version>
  75. </dependency>
  76. <dependency>
  77. <groupId>org.freemarker</groupId>
  78. <artifactId>freemarker</artifactId>
  79. <version>2.3.29</version>
  80. </dependency>
  81.  
  82. <!-- druid -->
  83. <dependency>
  84. <groupId>com.alibaba</groupId>
  85. <artifactId>druid-spring-boot-starter</artifactId>
  86. <version>${druid.version}</version>
  87. </dependency>
  88. <!--mysql-->
  89. <dependency>
  90. <groupId>mysql</groupId>
  91. <artifactId>mysql-connector-java</artifactId>
  92. <version>5.1.47</version>
  93. <scope>runtime</scope>
  94. </dependency>
  95.  
  96. <!--commons-lang3-->
  97. <dependency>
  98. <groupId>org.apache.commons</groupId>
  99. <artifactId>commons-lang3</artifactId>
  100. </dependency>
  101.  
  102. </dependencies>
  103.  
  104. <build>
  105. <plugins>
  106. <!--指定JDK编译版本 -->
  107. <plugin>
  108. <groupId>org.apache.maven.plugins</groupId>
  109. <artifactId>maven-compiler-plugin</artifactId>
  110. <configuration>
  111. <source>1.8</source>
  112. <target>1.8</target>
  113. <encoding>UTF-8</encoding>
  114. </configuration>
  115. </plugin>
  116. <!-- 打包跳过测试 -->
  117. <plugin>
  118. <groupId>org.apache.maven.plugins</groupId>
  119. <artifactId>maven-surefire-plugin</artifactId>
  120. <configuration>
  121. <skipTests>true</skipTests>
  122. </configuration>
  123. </plugin>
  124. <plugin>
  125. <groupId>org.springframework.boot</groupId>
  126. <artifactId>spring-boot-maven-plugin</artifactId>
  127. </plugin>
  128. </plugins>
  129. </build>
  130.  
  131. </project>

application.yml :

  1. server:
  2. port:
  3. spring:
  4. application:
  5. name: auth-server
  6. datasource:
  7. url: jdbc:mysql://localhost:3306/db_oauth?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
  8. username: root
  9. password: root
  10. driver-class-name: com.mysql.jdbc.Driver
  11.  
  12. #mybatis plus 设置
  13. mybatis-plus:
  14. mapper-locations: classpath*:mapper/*Mapper.xml
  15. global-config:
  16. # 关闭MP3.0自带的banner
  17. banner: false
  18. db-config:
  19. #主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
  20. id-type: 0
  21. # 默认数据库表下划线命名
  22. table-underline: true
  23. configuration:
  24. # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
  25. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

 

下面开始新建认证服务器的配置类,写代码之前,先来看一下下图,

作为认证服务器,OAuth2 协议里的其他几个角色他都要知道,认证服务器都要知道各个角色都是谁,他们各自的特征是什么。

1,要知道有哪些客户端应用 来申请令牌

2,要知道有哪些合法的用户

3,要知道发出去的令牌,能够访问哪些资源服务器

 写代码

我们需要新建一个认证服务器配置类   OAuth2AuthServerConfig继承 AuthorizationServerConfigurerAdapter ,AuthorizationServerConfigurerAdapter 是认证服务器适配器,我们看一下的源码:

  1. /**
  2. * @author Dave Syer
  3. *
  4. */
  5. public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
  6.  
  7. @Override
  8. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  9. }
  10.  
  11. @Override
  12. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  13. }
  14.  
  15. @Override
  16. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  17. }
  18.  
  19. }

里面有三个方法,这三个方法,正对应上图中箭头所指的三个问题,我们需要重写这三个方法,实现自己的配置。

1,配置Client信息    

  从图中可以看出,认证服务器要配置两个Client,一个是【客户端应用】,他需要来认证服务器申请令牌,一个是 【订单服务】,他要来认证服务器验令牌

重写AuthorizationServerConfigurerAdapter 的   configure(ClientDetailsServiceConfigurer clients) throws Exception 方法

  1. /**
  2. * Created by: 李浩洋 on 2019-10-29
  3. *
  4. * 认证服务器
  5. **/
  6. @Configuration //这是一个配置类
  7. @EnableAuthorizationServer //当前应用是一个认证服务器
  8. public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {//AuthorizationServerConfigurerAdapter:认证服务器适配器
  9.  
  10. //Spring 对密码加密的封装,自己配置下
  11. @Autowired
  12. private PasswordEncoder passwordEncoder;
  13.  
  14. /**
  15. * 配置客户端应用的信息,让认证服务器知道有哪些客户端应用来申请令牌。
  16. *
  17. * ClientDetailsServiceConfigurer:客户端的详情服务的配置
  18. * @param clients
  19. * @throws Exception
  20. */
  21. @Override
  22. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  23. clients.inMemory()//配置在内存里,后面修改为数据库里
  24. //~============== 注册【客户端应用】,使客户端应用能够访问认证服务器 ===========
  25. .withClient("orderApp")
  26. .secret(passwordEncoder.encode("123456")) //spring
  27. .scopes("read","write") //orderApp有哪些权限
  28. .accessTokenValiditySeconds(3600) //token的有效期
  29. .resourceIds("order-server") //资源服务器的id。发给orderApp的token,能访问哪些资源服务器,可以多个
  30. .authorizedGrantTypes("password")//授权方式,再给orderApp做授权的时候可以用哪种授权方式授权
  31. //~=============客户端应用配置结束 =====================
  32. .and()
  33. //~============== 注册【资源服务器-订单服务】(因为订单服务需要来认证服务器验令牌),使订单服务也能够访问认证服务器 ===========
  34. .withClient("orderServer")
  35. .secret(passwordEncoder.encode("123456")) //spring
  36. .scopes("read","write") //有哪些权限
  37. .accessTokenValiditySeconds(3600) //token的有效期
  38. .resourceIds("order-server") //资源服务器的id
  39. .authorizedGrantTypes("password");//授权方式
  40. }
  41.  
  42. }

 2,配置用户 信息

 告诉认证服务器,有哪些用户可以来访问认证服务器

重写AuthorizationServerConfigurerAdapter 的   configure(AuthorizationServerEndpointsConfigurer endpoints)   方法

  1. /**
  2. * Created by: 李浩洋 on 2019-10-29
  3. *
  4. * 认证服务器
  5. **/
  6. @Configuration //这是一个配置类
  7. @EnableAuthorizationServer //当前应用是一个认证服务器
  8. public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {//AuthorizationServerConfigurerAdapter:认证服务器适配器
  9.  
  10. //Spring 对密码加密的封装,自己配置下
  11. @Autowired
  12. private PasswordEncoder passwordEncoder;
  13.  
  14. @Autowired
  15. private AuthenticationManager authenticationManager;
  16.  
  17. /**
  18. * 1,配置客户端应用的信息,让认证服务器知道有哪些客户端应用来申请令牌。
  19. *
  20. * ClientDetailsServiceConfigurer:客户端的详情服务的配置
  21. * @param clients
  22. * @throws Exception
  23. */
  24. @Override
  25. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  26. clients.inMemory()//配置在内存里,后面修改为数据库里
  27. //~============== 注册【客户端应用】,使客户端应用能够访问认证服务器 ===========
  28. .withClient("orderApp")
  29. .secret(passwordEncoder.encode("123456")) //spring
  30. .scopes("read","write") //orderApp有哪些权限
  31. .accessTokenValiditySeconds(3600) //token的有效期
  32. .resourceIds("order-server") //资源服务器的id。发给orderApp的token,能访问哪些资源服务器,可以多个
  33. .authorizedGrantTypes("password")//授权方式,再给orderApp做授权的时候可以用哪种授权方式授权
  34. //~=============客户端应用配置结束 =====================
  35. .and()
  36. //~============== 注册【资源服务器-订单服务】(因为订单服务需要来认证服务器验令牌),使订单服务也能够访问认证服务器 ===========
  37. .withClient("orderServer")
  38. .secret(passwordEncoder.encode("123456")) //spring
  39. .scopes("read","write") //orderServer有哪些权限
  40. .accessTokenValiditySeconds(3600) //token的有效期
  41. .resourceIds("order-server") //资源服务器的id。
  42. .authorizedGrantTypes("password");//授权方式,
  43. }
  44.  
  45. /**
  46. *,2,配置用户信息
  47. * @param endpoints
  48. * @throws Exception
  49. */
  50. @Override
  51. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  52. //传给他一个authenticationManager用来校验传过来的用户信息是不是合法的,注进来一个,自己实现
  53. endpoints.authenticationManager(authenticationManager);
  54. }
  55.  
  56. /**
  57. * 3,配置资源服务器过来验token 的规则
  58. * @param security
  59. * @throws Exception
  60. */
  61. @Override
  62. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  63. /**
  64. * 过来验令牌有效性的请求,不是谁都能验的,必须要是经过身份认证的。
  65. * 所谓身份认证就是,必须携带clientId,clientSecret,否则随便一请求过来验token是不验的
  66. */
  67. security.checkTokenAccess("isAuthenticated()");
  68. }
  69. }

上边的 加密解密类 PasswordEncoder  和  配置用户信息的 AuthenticationManager 还没有地方来,下边配置这俩类。

新建配置类 OAuth2WebSecurityConfig 继承 WebSecurityConfigurerAdapter

  1. package com.nb.security.server.auth;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.Lazy;
  7. import org.springframework.security.authentication.AuthenticationManager;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  10. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  11. import org.springframework.security.core.userdetails.UserDetailsService;
  12. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  13. import org.springframework.security.crypto.password.PasswordEncoder;
  14.  
  15. /**
  16. * Created by: 李浩洋 on 2019-10-29
  17. **/
  18. @Configuration
  19. @EnableWebSecurity //使安全配置生效
  20. public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
  21.  
  22. @Autowired
  23. private UserDetailsService userDetailsService;
  24.  
  25. @Autowired
  26. private PasswordEncoder passwordEncoder;
  27.  
  28. /**
  29. * AuthenticationManagerBuilder 是用来构建 AuthenticationManager(处理登录操作)的
  30. * 需要两个东西:userDetailsService 、passwordEncoder
  31. * @param auth
  32. * @throws Exception
  33. */
  34. @Override
  35. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  36. auth.userDetailsService(userDetailsService) //获取用户信息
  37. .passwordEncoder(passwordEncoder); //比对密码
  38. }
  39.  
  40. /**
  41. * 把AuthenticationManager暴露为bean
  42. * @return
  43. * @throws Exception
  44. */
  45. @Bean
  46. @Override
  47. public AuthenticationManager authenticationManagerBean() throws Exception {
  48. return super.authenticationManagerBean();
  49. }
  50. }

+++++++++++++++ 备注 ++++++++++++++++++++++++++

2019-12-15-23:00, passwordEncoder 本应该配置在上述类里,但是配置后报错循环依赖,暂时将其写在启动类里,不报错

+++++++++++++++++++++++++++++++++++++++++++++

UserDetailsService接口

UserDetailsService接口,只有一个方法,返回UserDetails 接口:

  1. public interface UserDetailsService {
  2.  
  3. UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
  4. }
  1.  
  1. loadUserByUsername,这里不用比对密码,比对密码是在AuthenticationManager里做的。
  1. UserDetails接口如下,提供了一些见名知意的方法,我们需要自定义自己的UserDetails实现类,比如你的User类实现这个接口 :
  1. public interface UserDetails extends Serializable {
  2. // ~ Methods
  3. // ========================================================================================================
  4.  
  5. Collection<? extends GrantedAuthority> getAuthorities();
  6.  
  7. String getPassword();
  8.  
  9. String getUsername();
  10.  
  11. boolean isAccountNonExpired();
  12.  
  13. boolean isAccountNonLocked();
  14.  
  15. boolean isCredentialsNonExpired();
  16.  
  17. boolean isEnabled();
  18. }

自定义UserDetailsService 实现类:

  1. /**
  2. * Created by: 李浩洋 on 2019-10-29
  3. **/
  4. @Component//TODO:这里不写 ("userDetailsService")
  5. public class UserDetailsServiceImpl implements UserDetailsService {
  6.  
  7. /**
  8. *
  9. */
  10. @Autowired
  11. private PasswordEncoder passwordEncoder;
  12.  
  13. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  14.  
  15. return User.withUsername(username)
  16. .password(passwordEncoder.encode("123456"))
  17. .authorities("ROLE_ADMIN") //权限
  18. .build();//构建一个User对象
  19. }
  20. }

AuthenticationManager 接口

也只有一个方法,实现类一般不需要自己实现。入参 是一个 Authentication 接口的实现,其中封装了认证的信息,不同的认证发的信息不一样,如用户名/密码 登录需要用户名密码,OAuth2则需要appId,appSecret,redirectURI等,

不同的认证方式传过来的实现不同, authenticate 方法验证完了后将其中的信息更新调,返回。

  1. public interface AuthenticationManager {
  2.  
  3. Authentication authenticate(Authentication authentication)
  4. throws AuthenticationException;
  5. }

所有的准备工作都做好了,下面启动应用,来申请一个OAuth2令牌

postman请求http://localhost:9090/oauth/token ,HttpBasic传入客户端id和客户端密码。

用户名随便输,密码要和UserDetailsService一致,grant_type是说OAuth2的授权类型是授权码类型,scope是该客户端申请的权限,必须在Client配置里面包含

输入错误的密码:

输入不存在的scope:

代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-4-4-andbefore

+++++++++++++分割线++++++++++++++++++++++

小结

用图片生动解释了 OAuth2 中的角色和Spring接口 AuthorizationServerConfigurerAdapter 三个方法的关系,方便记忆

实现了OAuth2的密码模式,来申请token

存在问题:passwordEncoder 如果放在了 OAuth2WebSecurityConfig配置类里面,就会报循环依赖错误,有待解决

Spring Cloud微服务安全实战_4-4_OAuth2协议与微服务安全的更多相关文章

  1. Spring cloud微服务安全实战-4-4 OAuth2协议与微服务安全

    Oauth2 解决了cookie和session的问题 搭建认证服务器 把依赖都复制进来 因为搭建的是Oauth的服务器,所以还需要导入oauth2 开始写代码 首先创建启动类 增加配置文件 端口设置 ...

  2. spring cloud: zuul(二): zuul的serviceId/service-id配置(微网关)

    spring cloud: zuul(二): zuul的serviceId/service-id配置(微网关) zuul: routes: #路由配置表示 myroute1: #路由名一 path: ...

  3. 新书上线:《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》,欢迎大家买回去垫椅子垫桌脚

    新书上线 大家好,笔者的新书<Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统>已上线,此书内容充实.材质优良,乃家中必备垫桌脚 ...

  4. Spring Cloud Gateway限流实战

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. Spring Cloud架构教程 (七)消息驱动的微服务(核心概念)【Dalston版】

    下图是官方文档中对于Spring Cloud Stream应用模型的结构图.从中我们可以看到,Spring Cloud Stream构建的应用程序与消息中间件之间是通过绑定器Binder相关联的,绑定 ...

  6. Spring Cloud架构教程 (六)消息驱动的微服务【Dalston版】

    Spring Cloud Stream是一个用来为微服务应用构建消息驱动能力的框架.它可以基于Spring Boot来创建独立的.可用于生产的Spring应用程序.它通过使用Spring Integr ...

  7. Spring Cloud架构教程 (八)消息驱动的微服务(消费组)【Dalston版】

    使用消费组实现消息消费的负载均衡 通常在生产环境,我们的每个服务都不会以单节点的方式运行在生产环境,当同一个服务启动多个实例的时候,这些实例都会绑定到同一个消息通道的目标主题(Topic)上. 默认情 ...

  8. Spring Cloud Sleuth超详细实战

    为什么需要Spring Cloud Sleuth 微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元.由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去 ...

  9. spring cloud 入门系列四:使用Hystrix 实现断路器进行服务容错保护

    在微服务中,我们将系统拆分为很多个服务单元,各单元之间通过服务注册和订阅消费的方式进行相互依赖.但是如果有一些服务出现问题了会怎么样? 比如说有三个服务(ABC),A调用B,B调用C.由于网络延迟或C ...

  10. spring cloud 入门系列五:使用Feign 实现声明式服务调用

    一.Spring Cloud Feign概念引入通过前面的随笔,我们了解如何通过Spring Cloud ribbon进行负责均衡,如何通过Spring Cloud Hystrix进行服务断路保护,两 ...

随机推荐

  1. Paper | Spatially Adaptive Computation Time for Residual Networks

    目录 摘要 故事 SACT机制 ACT机制 SACT机制 实验 发表在2017年CVPR. 摘要 在图像检测任务中,对于图像不同的区域,我们可以分配不同层数的网络予以处理. 本文就提出了一个基于Res ...

  2. HTTP和RPC是现代微服务架构,HTTP和RPC是现代微服务架构

    .NET Core使用gRPC打造服务间通信基础设施   一.什么是RPC rpc(远程过程调用)是一个古老而新颖的名词,他几乎与http协议同时或更早诞生,也是互联网数据传输过程中非常重要的传输机制 ...

  3. NXP官方的i.mx6ul板级uboot源码适配

    1.前言 CoM-P6UL是盈鹏飞科技有限公司基于NXP原厂I.MX6UL芯片生产研发的核心板,本文将对CoM-P6UL适配NXP的基于Linux4.1.15版本的uboot板级源码. 2.开发环境 ...

  4. MongoDB副本集--Secondary节点实例恢复

    场景描述 MongoDB副本集中有一台Secondary节点出现RECOVERING的状态 状态如下: arps:RECOVERING> rs.status() { "set" ...

  5. mysql8 安装

    准备工作: 首先安装这些依赖 yum install -y flex yum install gcc gcc-c++ cmake  ncurses ncurses-devel bison libaio ...

  6. MySQL使用crontab定时备份不执行问题

    在使用crontab定时备份数据库时,发现并没有执行备份命令. 下面是定时备份的代码: 30 1 * * * /usr/local/mysql/bin/mysqldump --defaults-ext ...

  7. 一个比 AutoMapper 更快的模型映射的组件 Mapster

    下面是官方的性能测试 Demo,感性的也可以去 Github 上下载. 贴出代码目的是如果后期直接从自己的博客中在线看. using System; using System.Collections. ...

  8. 【机器学习笔记】ID3构建决策树

    好多算法之类的,看理论描述,让人似懂非懂,代码走一走,现象就了然了. 引: from sklearn import tree names = ['size', 'scale', 'fruit', 'b ...

  9. 使用WebApi和Asp.Net Core Identity 认证 Blazor WebAssembly(Blazor客户端应用)

    原文:https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-web ...

  10. MAC bash和zsh切换

    bash和zsh切换 切换到bash chsh -s /bin/bash 切换到zsh chsh -s /bin/zsh 记得输入切换命令后,要重新打开终端terminal才生效哦!大功告成!