Spring Security

Spring Security 是 Spring Resource 社区的一个安全组件。在安全方面,有两个主要的领域,一是“认证”,即你是谁;二是“授权”,即你拥有什么权限,Spring Security 的主要目标就是在这两个领域

Spring OAuth2

OAuth2 是一个标准的授权协议,允许不同的客户端通过认证和授权的形式来访问被其保护起来的资源

OAuth2 协议在 Spring Resource 中的实现为 Spring OAuth2,Spring OAuth2 分为:OAuth2 Provider 和 OAuth2 Client

OAuth2 Provider

OAuth2 Provider 负责公开被 OAuth2 保护起来的资源

OAuth2 Provider 需要配置代表用户的 OAuth2 客户端信息,被用户允许的客户端就可以访问被 OAuth2 保护的资源。OAuth2 Provider 通过管理和验证 OAuth2 令牌来控制客户端是否有权限访问被其保护的资源

另外,OAuth2 Provider 还必须为用户提供认证 API 接口。根据认证 API 接口,用户提供账号和密码等信息,来确认客户端是否可以被 OAuth2 Provider 授权。这样做的好处就是第三方客户端不需要获取用户的账号和密码,通过授权的方式就可以访问被 OAuth2 保护起来的资源

OAuth2 Provider 的角色被分为 Authorization Service (授权服务) 和 Resource Service (资源服务),通常它们不在同一个服务中,可能一个 Authorization Service 对应多个 Resource Service

Spring OAuth2 需配合 Spring Security 一起使用,所有的请求由 Spring MVC 控制器处理,并经过一系列的 Spring Security 过滤器

在 Spring Security 过滤器链中有以下两个节点,这两个节点是向 Authorization Service 获取验证和授权的:

  1. 授权节点:默认为 /oauth/authorize
  2. 获取 Token 节点:默认为 /oauth/token

OAuth2 Client

OAuth2 Client (客户端) 用于访问被 OAuth2 保护起来的资源

新建 spring-security-oauth2-server

pom

  1. <parent>
  2. <artifactId>spring-cloud-parent</artifactId>
  3. <groupId>com.karonda</groupId>
  4. <version>1.0.0</version>
  5. </parent>
  6. <modelVersion>4.0.0</modelVersion>
  7. <artifactId>spring-security-oauth2-server</artifactId>
  8. <dependencies>
  9. <dependency>
  10. <groupId>org.springframework.cloud</groupId>
  11. <artifactId>spring-cloud-starter-oauth2</artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-data-rest</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-data-jpa</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>mysql</groupId>
  27. <artifactId>mysql-connector-java</artifactId>
  28. </dependency>
  29. </dependencies>
  30. <build>
  31. <plugins>
  32. <plugin>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-maven-plugin</artifactId>
  35. </plugin>
  36. </plugins>
  37. </build>

application.yml

  1. server:
  2. port: 8081
  3. servlet:
  4. context-path: /uaa # User Account and Authentication
  5. eureka:
  6. client:
  7. service-url:
  8. defaultZone: http://localhost:8001/eureka/
  9. spring:
  10. application:
  11. name: oauth2-server
  12. datasource:
  13. driver-class-name: com.mysql.cj.jdbc.Driver
  14. url: jdbc:mysql://localhost:3306/spring-security-auth2?useSSL=false
  15. username: root
  16. password: root
  17. hikari:
  18. maximum-pool-size: 20
  19. minimum-idle: 5
  20. jpa:
  21. database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
  22. show-sql: true
  23. hibernate:
  24. ddl-auto: update
  25. main:
  26. allow-bean-definition-overriding: true

数据表

创建用户和角色及中间表:

  1. DROP TABLE IF EXISTS role;
  2. CREATE TABLE role
  3. (
  4. id bigint(20) NOT NULL AUTO_INCREMENT,
  5. name varchar(255) NOT NULL,
  6. PRIMARY KEY (id)
  7. );
  8. DROP TABLE IF EXISTS user;
  9. CREATE TABLE user
  10. (
  11. id bigint(20) NOT NULL AUTO_INCREMENT,
  12. password varchar(255) DEFAULT NULL,
  13. username varchar(255) NOT NULL ,
  14. PRIMARY KEY (id ),
  15. UNIQUE KEY (username)
  16. );
  17. DROP TABLE IF EXISTS user_role;
  18. CREATE TABLE user_role (
  19. user_id bigint(20) NOT NULL,
  20. role_id bigint(20) NOT NULL,
  21. KEY (user_id),
  22. KEY (role_id),
  23. FOREIGN KEY (user_id) REFERENCES user (id),
  24. FOREIGN KEY (role_id) REFERENCES role (id)
  25. );
  26. `

OAuth2 Client 信息可以存储在数据库中,Spring OAuth2 已经设计好了数据表,且不可变,创建数据表的脚本:schema.sql (如果使用 MySQL 需要将 LONGVARBINARY 替换为 BLOB)

初始化数据

  1. INSERT INTO role (name) VALUES ('ROLE_USER');
  2. INSERT INTO role (name) VALUES ('ROLE_ADMIN');
  3. INSERT INTO user (username, password) VALUES ('test', '123');
  4. INSERT INTO user_role (user_id, role_id) VALUES (1, 1);

Entity & Dao & Service

  1. @Entity
  2. public class Role implements GrantedAuthority {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private Long id;
  6. @Column(nullable = false)
  7. private String name;
  8. public Long getId() {
  9. return id;
  10. }
  11. public void setId(Long id) {
  12. this.id = id;
  13. }
  14. @Override
  15. public String getAuthority() {
  16. return name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. @Override
  22. public String toString(){
  23. return name;
  24. }
  25. }

Role 实现了 GrantedAuthority 接口

  1. @Entity
  2. public class User implements UserDetails, Serializable {
  3. @Id
  4. @GeneratedValue(strategy = GenerationType.IDENTITY)
  5. private Long id;
  6. @Column(nullable = false, unique = true)
  7. private String username;
  8. @Column
  9. private String password;
  10. @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  11. @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
  12. inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
  13. private List<Role> authorities;
  14. public User(){
  15. }
  16. public Long getId() {
  17. return id;
  18. }
  19. public void setId(Long id) {
  20. this.id = id;
  21. }
  22. @Override
  23. public String getUsername() {
  24. return username;
  25. }
  26. public void setUsername(String username) {
  27. this.username = username;
  28. }
  29. @Override
  30. public String getPassword() {
  31. return password;
  32. }
  33. public void setPassword(String password) {
  34. this.password = password;
  35. }
  36. @Override
  37. public Collection<? extends GrantedAuthority> getAuthorities() {
  38. return authorities;
  39. }
  40. public void setAuthorities(List<Role> authorities) {
  41. this.authorities = authorities;
  42. }
  43. @Override
  44. public boolean isAccountNonExpired(){
  45. return true;
  46. }
  47. @Override
  48. public boolean isAccountNonLocked(){
  49. return true;
  50. }
  51. @Override
  52. public boolean isCredentialsNonExpired(){
  53. return true;
  54. }
  55. @Override
  56. public boolean isEnabled(){
  57. return true;
  58. }
  59. }

User 实现了 UserDetails 接口,该接口是 Spring Security 认证信息的核心接口

  1. public interface UserDao extends JpaRepository<User, Long> {
  2. User findByUsername(String username);
  3. }
  1. @Service
  2. public class UserServiceDetail implements UserDetailsService {
  3. @Autowired
  4. private UserDao userDao;
  5. @Override
  6. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  7. return userDao.findByUsername(username);
  8. }
  9. }

UserServiceDetail 实现了 UserDetailsService 接口

启动类

  1. @EnableEurekaClient
  2. @SpringBootApplication
  3. public class Oauth2ServerApp {
  4. public static void main(String[] args){
  5. SpringApplication.run(Oauth2ServerApp.class, args);
  6. }
  7. }

Spring Security 配置

  1. @Configuration
  2. @EnableWebSecurity // 开启 Spring Security
  3. @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别上的保护
  4. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  5. @Autowired
  6. private UserServiceDetail userServiceDetail;
  7. @Bean
  8. public PasswordEncoder passwordEncoder() {
  9. return NoOpPasswordEncoder.getInstance();
  10. }
  11. @Override
  12. protected void configure(HttpSecurity http) throws Exception {
  13. http.authorizeRequests()
  14. .anyRequest().authenticated() // 所有请求都需要安全验证
  15. .and()
  16. .csrf().disable();
  17. }
  18. @Override
  19. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  20. auth.userDetailsService(userServiceDetail).passwordEncoder(passwordEncoder());
  21. }
  22. @Override
  23. @Bean
  24. public AuthenticationManager authenticationManagerBean() throws Exception {
  25. return super.authenticationManagerBean();
  26. }
  27. }

Spring Security 5 使用 Spring Security 4 的配置会报 There is no PasswordEncoder mapped for the id “null” 异常,解决方法是使用 NoOpPasswordEncoder (临时解决方案,非最优方案)

—- 2020-04-24 更新开始 —-

PasswordEncoder 可以使用自定义 Encoder:

  1. @Bean
  2. public PasswordEncoder passwordEncoder() {
  3. return new MyPasswordEncoder();
  4. }

MyPasswordEncoder 代码:

  1. public class MyPasswordEncoder implements PasswordEncoder {
  2. @Override
  3. public String encode(CharSequence charSequence) {
  4. return PasswordUtil.getencryptPassword((String)charSequence);
  5. }
  6. @Override
  7. public boolean matches(CharSequence charSequence, String s) {
  8. boolean result = PasswordUtil.matches(charSequence, s);
  9. return result;
  10. }
  11. }

PasswordUtil 代码:

  1. public class PasswordUtil {
  2. private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  3. public static String getencryptPassword(String password){
  4. return encoder.encode(password);
  5. }
  6. public static boolean matches(CharSequence rawPassword, String encodedPassword){
  7. return encoder.matches(rawPassword, encodedPassword);
  8. }
  9. }

—- 2020-04-24 更新结束 —-

Authorization Server 配置

  1. @Configuration
  2. @EnableAuthorizationServer // 开启授权服务
  3. @EnableResourceServer // 需要对外暴露获取和验证 Token 的接口,所以也是一个资源服务
  4. public class OAuth2Config extends AuthorizationServerConfigurerAdapter{
  5. @Autowired
  6. private DataSource dataSource;
  7. @Autowired
  8. private AuthenticationManager authenticationManager;
  9. @Autowired
  10. private UserServiceDetail userServiceDetail;
  11. @Override
  12. // 配置客户端信息
  13. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  14. clients.inMemory() // 将客户端的信息存储在内存中
  15. .withClient("browser") // 客户端 id, 需唯一
  16. .authorizedGrantTypes("refresh_token", "password") // 认证类型为 refresh_token, password
  17. .scopes("ui") // 客户端域
  18. .and()
  19. .withClient("eureka-client") // 另一个客户端
  20. .secret("123456") // 客户端密码
  21. .authorizedGrantTypes("client_credentials", "refresh_token", "password")
  22. .scopes("server");
  23. }
  24. @Override
  25. // 配置授权 token 的节点和 token 服务
  26. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  27. endpoints.tokenStore(tokenStore()) // token 的存储方式
  28. .authenticationManager(authenticationManager) // 开启密码验证,来源于 WebSecurityConfigurerAdapter
  29. .userDetailsService(userServiceDetail); // 读取验证用户的信息
  30. }
  31. @Override
  32. // 配置 token 节点的安全策略
  33. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  34. security.tokenKeyAccess("permitAll()") // 获取 token 的策略
  35. .checkTokenAccess("isAuthenticated()");
  36. }
  37. @Bean
  38. public TokenStore tokenStore() {
  39. // return new InMemoryTokenStore();
  40. return new JdbcTokenStore(dataSource);
  41. }
  42. }

RemoteTokenServices 接口

  1. @RestController
  2. @RequestMapping("/users")
  3. public class UserController {
  4. @RequestMapping(value = "/current", method = RequestMethod.GET)
  5. public Principal getUser(Principal principal){
  6. return principal;
  7. }
  8. }

本文采用 RemoteTokenServices 这种方式对 Token 进行验证,如果其他资源服务需要验证 Token 则需要远程调用授权服务暴露的验证 Token 的 API 接口

测试

  1. 启动 eureka-server
  2. 启动 oauth2-server

使用 Postman 测试:

     
- POST http://localhost:8081/uaa/oauth/token
Headers    
- Authorization Basic ZXVyZWthLWNsaWVudDoxMjM0NTY=
Body    
- username test
- password 123
- grant_type password

其中 Authorization 的值为 Basic clientId:secret (本文中为 eureka-client:123456) Base64 加密后的值

返回结果:

  1. {
  2. "access_token": "5dc978ab-8c7e-4286-92f5-5655b8d15c98",
  3. "token_type": "bearer",
  4. "refresh_token": "7ef02b1c-6e8a-485f-adc9-18a48c2ae410",
  5. "expires_in": 43199,
  6. "scope": "server"
  7. }

eureka-client

添加依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-oauth2</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-starter-openfeign</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-data-jpa</artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>mysql</groupId>
  15. <artifactId>mysql-connector-java</artifactId>
  16. </dependency>

application.xml 添加

  1. security:
  2. oauth2:
  3. resource:
  4. user-info-uri: http://localhost:8081/uaa/users/current
  5. client:
  6. client-id: eureka-client
  7. client-secret: 123456
  8. access-token-uri: http://localhost:8081/uaa/oauth/token
  9. grant-type: client_credentials, password
  10. scope: server

数据库配置同 oauth2-server 未列出

配置 Resource Server

  1. @Configuration
  2. @EnableResourceServer // 开启资源服务
  3. @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别上的保护
  4. public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
  5. @Override
  6. public void configure(HttpSecurity http) throws Exception {
  7. http.authorizeRequests()
  8. .antMatchers("/user/register").permitAll()
  9. .anyRequest().authenticated();
  10. }
  11. }

配置 OAuth2 Client

  1. @EnableOAuth2Client // 开启 OAuth2 Client
  2. @EnableConfigurationProperties
  3. @Configuration
  4. public class OAuth2ClientConfig {
  5. @Bean
  6. @ConfigurationProperties(prefix = "security.oauth2.client")
  7. // 配置受保护的资源信息
  8. public ClientCredentialsResourceDetails clientCredentialsResourceDetails(){
  9. return new ClientCredentialsResourceDetails();
  10. }
  11. @Bean
  12. // 过滤器,存储当前请求和上下文
  13. public RequestInterceptor oAuth2FeignRequestInterceptor(){
  14. return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
  15. }
  16. @Bean
  17. public OAuth2RestTemplate clientCredentialsRestTemplate (){
  18. return new OAuth2RestTemplate(clientCredentialsResourceDetails());
  19. }
  20. }

Entity & Dao & Service

与 oauth2-server 类似,具体见代码

Controller

  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4. @Autowired
  5. private UserService userService;
  6. @RequestMapping(value = "/register", method = RequestMethod.POST)
  7. public User createUser(@RequestParam("username") String username
  8. , @RequestParam("password") String password){
  9. return userService.create(username, password);
  10. }
  11. }
  1. @RestController
  2. public class HiController {
  3. @Value("${server.port}")
  4. int port;
  5. @Value("${version}")
  6. String version;
  7. @GetMapping("/hi")
  8. public String home(@RequestParam String name){
  9. return "Hello " + name + ", from port: " + port + ", version: " + version;
  10. }
  11. @PreAuthorize("hasAuthority('ROLE_ADMIN')") // 需要权限
  12. @RequestMapping("/hello")
  13. public String hello(){
  14. return "hello!";
  15. }
  16. }

测试

  1. 启动 eureka-server
  2. 启动 oauth2-server
  3. 启动 config-server
  4. 启动 eureka-client

使用 Postman 测试:

注册用户

     
- POST localhost:8011/user/register
Body    
- username admin
- password 123

返回结果:

  1. {
  2. "id": 2,
  3. "username": "admin",
  4. "password": "123",
  5. "authorities": null,
  6. "enabled": true,
  7. "accountNonExpired": true,
  8. "credentialsNonExpired": true,
  9. "accountNonLocked": true
  10. }

请求 token

     
- POST http://localhost:8081/uaa/oauth/token
Headers    
- Authorization Basic ZXVyZWthLWNsaWVudDoxMjM0NTY=
Body    
- username admin
- password 123
- grant_type password
  1. {
  2. "access_token": "dc4959fb-9ced-430e-9a78-5e9609c3baac",
  3. "token_type": "bearer",
  4. "refresh_token": "06cdcf55-fe6a-4367-94e1-051c2da86e37",
  5. "expires_in": 43199,
  6. "scope": "server"
  7. }

访问不需要权限的接口

     
- GET localhost:8011/hi?name=Victor
Headers    
- Authorization Bearer dc4959fb-9ced-430e-9a78-5e9609c3baac
  1. Hello Victor, from port: 8011, version: 1.0.2

访问需要权限的接口

     
- GET localhost:8011/hello
Headers    
- Authorization Bearer dc4959fb-9ced-430e-9a78-5e9609c3baac
  1. {
  2. "error": "access_denied",
  3. "error_description": "不允许访问"
  4. }

手动授权:

  1. INSERT INTO user_role (user_id, role_id) VALUES (2, 2);

重新获取 token 后再次访问接口

  1. hello!

完整代码:GitHub

本人 C# 转 Java 的 newbie, 如有错误或不足欢迎指正,谢谢

Spring Cloud 学习 (九) Spring Security, OAuth2的更多相关文章

  1. Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)

    Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...

  2. Spring Cloud学习笔记--Spring Boot初次搭建

    1. Spring Boot简介 初次接触Spring的时候,我感觉这是一个很难接触的框架,因为其庞杂的配置文件,我最不喜欢的就是xml文件,这种文件的可读性很不好.所以很久以来我的Spring学习都 ...

  3. Spring Cloud 学习 之 Spring Cloud Eureka(搭建)

    Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 文章目录 搭建服务注册中心: 注册服务提供者: 高可用注册中心: 搭建服务注册中心: ...

  4. Spring Cloud 学习 (十) Spring Security, OAuth2, JWT

    通过 Spring Security + OAuth2 认证和鉴权,每次请求都需要经过 OAuth Server 验证当前 token 的合法性,并且需要查询该 token 对应的用户权限,在高并发场 ...

  5. Spring Cloud 学习 之 Spring Cloud Ribbon(基础知识铺垫)

    文章目录 1.负载均衡: 2.RestTemplate详解: xxxForEntity/xxxForObject:主要介绍get跟post exchange: execute源码分析: 1.负载均衡: ...

  6. spring cloud学习(七)Spring Cloud Config(续)

    Spring Cloud Config(续) 个人参考项目 个人博客 : https://zggdczfr.cn/ 个人参考项目 : (整合到上一个案例中)https://github.com/Fun ...

  7. spring cloud学习(六)Spring Cloud Config

    Spring Cloud Config 参考个人项目 参考个人项目 : (希望大家能给个star~) https://github.com/FunriLy/springcloud-study/tree ...

  8. Spring Cloud 学习 之 Spring Cloud Bus实现修改远程仓库后配置自动刷新

    ​ 版本号: ​ Spring Boot:2.1.3.RELEASE ​ Spring Cloud:G版 ​ 开发工具:IDEA 搭建配置中心,这里我们搭建一个简单版的就行 POM: <?xml ...

  9. Spring Cloud学习 之 Spring Cloud Ribbon 重试机制及超时设置不生效

    今天测了一下Ribbon的重试跟超时机制,发现进行的全局超时配置一直不生效,配置如下: ribbon: #单位ms,请求连接的超时时间,默认1000 ConnectTimeout: 500 #单位ms ...

随机推荐

  1. html+canvas实现很真实的下雨雨落

    原素材地址:http://www.htmlsucai.com/demo-9782.html <!DOCTYPE html> <html> <head> <me ...

  2. 【Luogu】P1402 酒店之王 题解

    原题链接 这道题,很明显是个配对问题.于是,我们可以想到用网络最大流来做. 先整理一下题目条件. 很明显,根据贪心思想,要使最多人满意,每个人应该最多睡一个房间(似乎也没有人能睡两个房间),吃一道菜. ...

  3. Docker(12)- docker run 命令详解

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 作用 创建一个新的容器并运行一个 ...

  4. 5 MVVM

    1.概述 MVVM各个部分功能如下: Model:定义业务逻辑 View:定义面向用户接口,UI逻辑,处理用户交互请求 ViewModel:负责界面导航逻辑和应用状态管理,呈现逻辑. 1.1. 各司其 ...

  5. SpringBoot魔法堂:说说带智能提示的spring-boot-starter

    前言 前几个月和隔壁组的老王闲聊,他说项目的供应商离职率居高不下,最近还有开发刚接手ESB订阅发布接口才两周就提出离职,而他能做的就只有苦笑和默默地接过这个烂摊子了. 而然幸福的家庭总是相似的,而不幸 ...

  6. 也谈模块加载,吐槽CMD

    先吐槽CMD,不要没头没脑的搞出个CMD,没意思. 大家都看AMD好了,异步模块加载机制,CMD并没有改变这个模式. 模块加载的关口就是getCurrentScript,每次define被调用的时候, ...

  7. 深度探秘.NET 5.0

    今年11月10号 .NET 5.0 如约而至.这是.NET All in one后的第一个版本,虽然不是LTS(Long term support)版本,但是是生产环境可用的. 有微软的背书,微软从. ...

  8. 基于FFmpeg的Dxva2硬解码及Direct3D显示(二)

    解析视频源 目录 解析视频源 获取视频流 解析视频流 说明:这篇博文分为"获取视频流"和"解析视频流"两个部分,使用的是FFmpeg4.1的版本,与网上流传的低 ...

  9. TypeError: Cannot read property 'Component' of undefined

    继续跟着阮一峰的教程走,下面写到PropTypes的getDefaultProps时,又出现了问题,基于上一个createClass的报错换成了Component写法 错误描述: 解决方法:引入rea ...

  10. error while loading shared libraries解決方法

    在linux下运行程序时,发现了error while loading shared libraries这种错误,一时间不知道解决办法,在网上搜索,终于解决了. error while loading ...