单点登录(SSO),一看就会,一做就错!

服务器 开发工具
技术这东西吧,看别人写的好像很简单似的,到自己去写的时候就各种问题,“一看就会,一做就错”。

[[402841]]

图片来自 Pexels

网上关于实现 SSO 的文章一大堆,但是当你真的照着写的时候就会发现根本不是那么回事儿,简直让人抓狂,尤其是对于我这样的菜鸟。

几经曲折,终于搞定了,决定记录下来,以便后续查看。先来看一下效果:

准备

①单点登录

最常见的例子是,我们打开淘宝 APP,首页就会有天猫、聚划算等服务的链接,当你点击以后就直接跳过去了,并没有让你再登录一次。

下面这个图是我在网上找的,我觉得画得比较明白:

可惜有点儿不清晰,于是我又画了个简版的:

重要的是理解:

  • SSO 服务端和 SSO 客户端直接是通过授权以后发放 Token 的形式来访问受保护的资源。
  • 相对于浏览器来说,业务系统是服务端,相对于 SSO 服务端来说,业务系统是客户端。
  • 浏览器和业务系统之间通过会话正常访问。
  • 不是每次浏览器请求都要去 SSO 服务端去验证,只要浏览器和它所访问的服务端的会话有效它就可以正常访问。

利用 OAuth2 实现单点登录

接下来,只讲跟本例相关的一些配置,不讲原理,不讲为什么。

众所周知,在 OAuth2 在有授权服务器、资源服务器、客户端这样几个角色,当我们用它来实现 SSO 的时候是不需要资源服务器这个角色的,有授权服务器和客户端就够了。

授权服务器当然是用来做认证的,客户端就是各个应用系统,我们只需要登录成功后拿到用户信息以及用户所拥有的权限即可。

之前我一直认为把那些需要权限控制的资源放到资源服务器里保护起来就可以实现权限控制,其实是我想错了,权限控制还得通过 Spring Security 或者自定义拦截器来做。

①Spring Security 、OAuth2、JWT、SSO

在本例中,一定要分清楚这几个的作用:

首先,SSO 是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它。

其次,OAuth2 是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。

在本例实现 SSO 的过程中,受保护的资源就是用户的信息(包括,用户的基本信息,以及用户所具有的权限)。

而我们想要访问这这一资源就需要用户登录并授权,OAuth2 服务端负责令牌的发放等操作,这令牌的生成我们采用 JWT,也就是说 JWT 是用来承载用户的 Access_Token 的。

最后,Spring Security 是用于安全访问的,这里我们我们用来做访问权限控制。

认证服务器配置

Maven 依赖:

  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 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  4.     <modelVersion>4.0.0</modelVersion> 
  5.     <parent> 
  6.         <groupId>org.springframework.boot</groupId> 
  7.         <artifactId>spring-boot-starter-parent</artifactId> 
  8.         <version>2.1.3.RELEASE</version> 
  9.         <relativePath/> <!-- lookup parent from repository --> 
  10.     </parent> 
  11.     <groupId>com.cjs.sso</groupId> 
  12.     <artifactId>oauth2-sso-auth-server</artifactId> 
  13.     <version>0.0.1-SNAPSHOT</version> 
  14.     <name>oauth2-sso-auth-server</name
  15.  
  16.     <properties> 
  17.         <java.version>1.8</java.version> 
  18.     </properties> 
  19.  
  20.     <dependencies> 
  21.         <dependency> 
  22.             <groupId>org.springframework.boot</groupId> 
  23.             <artifactId>spring-boot-starter-data-jpa</artifactId> 
  24.         </dependency> 
  25.         <dependency> 
  26.             <groupId>org.springframework.boot</groupId> 
  27.             <artifactId>spring-boot-starter-data-redis</artifactId> 
  28.         </dependency> 
  29.         <dependency> 
  30.             <groupId>org.springframework.boot</groupId> 
  31.             <artifactId>spring-boot-starter-security</artifactId> 
  32.         </dependency> 
  33.         <dependency> 
  34.             <groupId>org.springframework.security.oauth.boot</groupId> 
  35.             <artifactId>spring-security-oauth2-autoconfigure</artifactId> 
  36.             <version>2.1.3.RELEASE</version> 
  37.         </dependency> 
  38.         <dependency> 
  39.             <groupId>org.springframework.boot</groupId> 
  40.             <artifactId>spring-boot-starter-thymeleaf</artifactId> 
  41.         </dependency> 
  42.         <dependency> 
  43.             <groupId>org.springframework.boot</groupId> 
  44.             <artifactId>spring-boot-starter-web</artifactId> 
  45.         </dependency> 
  46.         <dependency> 
  47.             <groupId>org.springframework.session</groupId> 
  48.             <artifactId>spring-session-data-redis</artifactId> 
  49.         </dependency> 
  50.         <dependency> 
  51.             <groupId>mysql</groupId> 
  52.             <artifactId>mysql-connector-java</artifactId> 
  53.             <scope>runtime</scope> 
  54.         </dependency> 
  55.         <dependency> 
  56.             <groupId>org.projectlombok</groupId> 
  57.             <artifactId>lombok</artifactId> 
  58.             <optional>true</optional> 
  59.         </dependency> 
  60.         <dependency> 
  61.             <groupId>org.springframework.boot</groupId> 
  62.             <artifactId>spring-boot-starter-test</artifactId> 
  63.             <scope>test</scope> 
  64.         </dependency> 
  65.         <dependency> 
  66.             <groupId>org.springframework.security</groupId> 
  67.             <artifactId>spring-security-test</artifactId> 
  68.             <scope>test</scope> 
  69.         </dependency> 
  70.  
  71.         <dependency> 
  72.             <groupId>org.apache.commons</groupId> 
  73.             <artifactId>commons-lang3</artifactId> 
  74.             <version>3.8.1</version> 
  75.         </dependency> 
  76.         <dependency> 
  77.             <groupId>com.alibaba</groupId> 
  78.             <artifactId>fastjson</artifactId> 
  79.             <version>1.2.56</version> 
  80.         </dependency> 
  81.  
  82.     </dependencies> 
  83.  
  84.     <build> 
  85.         <plugins> 
  86.             <plugin> 
  87.                 <groupId>org.springframework.boot</groupId> 
  88.                 <artifactId>spring-boot-maven-plugin</artifactId> 
  89.             </plugin> 
  90.         </plugins> 
  91.     </build> 
  92.  
  93. </project> 

这里面最重要的依赖是:spring-security-oauth2-autoconfigure。

application.yml:

  1. spring: 
  2.   datasource: 
  3.     url: jdbc:mysql://localhost:3306/permission 
  4.     username: root 
  5.     password: 123456 
  6.     driver-class-name: com.mysql.jdbc.Driver 
  7.   jpa: 
  8.     show-sql: true 
  9.   session: 
  10.     store-type: redis 
  11.   redis: 
  12.     host: 127.0.0.1 
  13.     password: 123456 
  14.     port: 6379 
  15. server: 
  16.   port: 8080 

AuthorizationServerConfig(重要):

  1. package com.cjs.sso.config; 
  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.Primary
  7. import org.springframework.security.core.token.DefaultToken; 
  8. import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 
  9. import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 
  10. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 
  11. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 
  12. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 
  13. import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 
  14. import org.springframework.security.oauth2.provider.token.TokenStore; 
  15. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 
  16. import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 
  17.  
  18. import javax.sql.DataSource; 
  19.  
  20. /** 
  21.  * @author ChengJianSheng 
  22.  * @date 2019-02-11 
  23.  */ 
  24. @Configuration 
  25. @EnableAuthorizationServer 
  26. public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 
  27.  
  28.     @Autowired 
  29.     private DataSource dataSource; 
  30.  
  31.     @Override 
  32.     public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 
  33.         security.allowFormAuthenticationForClients(); 
  34.         security.tokenKeyAccess("isAuthenticated()"); 
  35.     } 
  36.  
  37.     @Override 
  38.     public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 
  39.         clients.jdbc(dataSource); 
  40.     } 
  41.  
  42.     @Override 
  43.     public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 
  44.         endpoints.accessTokenConverter(jwtAccessTokenConverter()); 
  45.         endpoints.tokenStore(jwtTokenStore()); 
  46. //        endpoints.tokenServices(defaultTokenServices()); 
  47.     } 
  48.  
  49.     /*@Primary 
  50.     @Bean 
  51.     public DefaultTokenServices defaultTokenServices() { 
  52.         DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); 
  53.         defaultTokenServices.setTokenStore(jwtTokenStore()); 
  54.         defaultTokenServices.setSupportRefreshToken(true); 
  55.         return defaultTokenServices; 
  56.     }*/ 
  57.  
  58.     @Bean 
  59.     public JwtTokenStore jwtTokenStore() { 
  60.         return new JwtTokenStore(jwtAccessTokenConverter()); 
  61.     } 
  62.  
  63.     @Bean 
  64.     public JwtAccessTokenConverter jwtAccessTokenConverter() { 
  65.         JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); 
  66.         jwtAccessTokenConverter.setSigningKey("cjs");   //  Sets the JWT signing key 
  67.         return jwtAccessTokenConverter; 
  68.     } 
  69.  

说明:

  • 别忘了 @EnableAuthorizationServer。
  • Token 存储采用的是 JWT。
  • 客户端以及登录用户这些配置存储在数据库,为了减少数据库的查询次数,可以从数据库读出来以后再放到内存中。

WebSecurityConfig(重要):

  1. package com.cjs.sso.config; 
  2.  
  3. import com.cjs.sso.service.MyUserDetailsService; 
  4. import org.springframework.beans.factory.annotation.Autowired; 
  5. import org.springframework.context.annotation.Bean; 
  6. import org.springframework.context.annotation.Configuration; 
  7. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 
  8. import org.springframework.security.config.annotation.web.builders.HttpSecurity; 
  9. import org.springframework.security.config.annotation.web.builders.WebSecurity; 
  10. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 
  11. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 
  12. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 
  13. import org.springframework.security.crypto.password.PasswordEncoder; 
  14.  
  15. /** 
  16.  * @author ChengJianSheng 
  17.  * @date 2019-02-11 
  18.  */ 
  19. @Configuration 
  20. @EnableWebSecurity 
  21. public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
  22.  
  23.     @Autowired 
  24.     private MyUserDetailsService userDetailsService; 
  25.  
  26.     @Override 
  27.     protected void configure(AuthenticationManagerBuilder auth) throws Exception { 
  28.         auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 
  29.     } 
  30.  
  31.     @Override 
  32.     public void configure(WebSecurity web) throws Exception { 
  33.         web.ignoring().antMatchers("/assets/**""/css/**""/images/**"); 
  34.     } 
  35.  
  36.     @Override 
  37.     protected void configure(HttpSecurity http) throws Exception { 
  38.         http.formLogin() 
  39.                 .loginPage("/login"
  40.                 .and() 
  41.                 .authorizeRequests() 
  42.                 .antMatchers("/login").permitAll() 
  43.                 .anyRequest() 
  44.                 .authenticated() 
  45.                 .and().csrf().disable().cors(); 
  46.     } 
  47.  
  48.     @Bean 
  49.     public PasswordEncoder passwordEncoder() { 
  50.         return new BCryptPasswordEncoder(); 
  51.     } 
  52.  

自定义登录页面(一般来讲都是要自定义的):

  1. package com.cjs.sso.controller; 
  2.  
  3. import org.springframework.stereotype.Controller; 
  4. import org.springframework.web.bind.annotation.GetMapping; 
  5.  
  6. /** 
  7.  * @author ChengJianSheng 
  8.  * @date 2019-02-12 
  9.  */ 
  10. @Controller 
  11. public class LoginController { 
  12.  
  13.     @GetMapping("/login"
  14.     public String login() { 
  15.         return "login"
  16.     } 
  17.  
  18.     @GetMapping("/"
  19.     public String index() { 
  20.         return "index"
  21.     } 
  22.  

自定义登录页面的时候,只需要准备一个登录页面,然后写个 Controller 令其可以访问到即可,登录页面表单提交的时候 method 一定要是 post,最重要的时候 action 要跟访问登录页面的 url 一样。

千万记住了,访问登录页面的时候是 GET 请求,表单提交的时候是 POST 请求,其他的就不用管了。

  1. <!DOCTYPE html> 
  2. <html xmlns:th="http://www.thymeleaf.org"
  3. <head> 
  4.     <meta charset="utf-8"
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  6.     <title>Ela Admin - HTML5 Admin Template</title> 
  7.     <meta name="description" content="Ela Admin - HTML5 Admin Template"
  8.     <meta name="viewport" content="width=device-width, initial-scale=1"
  9.  
  10.     <link type="text/css" rel="stylesheet" th:href="@{/assets/css/normalize.css}"
  11.     <link type="text/css" rel="stylesheet" th:href="@{/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}"
  12.     <link type="text/css" rel="stylesheet" th:href="@{/assets/css/font-awesome.min.css}"
  13.     <link type="text/css" rel="stylesheet" th:href="@{/assets/css/style.css}"
  14.  
  15. </head> 
  16. <body class="bg-dark"
  17.  
  18. <div class="sufee-login d-flex align-content-center flex-wrap"
  19.     <div class="container"
  20.         <div class="login-content"
  21.             <div class="login-logo"
  22.                 <h1 style="color: #57bf95;">欢迎来到王者荣耀</h1> 
  23.             </div> 
  24.             <div class="login-form"
  25.                 <form th:action="@{/login}" method="post"
  26.                     <div class="form-group"
  27.                         <label>Username</label> 
  28.                         <input type="text" class="form-control" name="username" placeholder="Username"
  29.                     </div> 
  30.                     <div class="form-group"
  31.                         <label>Password</label> 
  32.                         <input type="password" class="form-control" name="password" placeholder="Password"
  33.                     </div> 
  34.                     <div class="checkbox"
  35.                         <label> 
  36.                             <input type="checkbox"> Remember Me 
  37.                         </label> 
  38.                         <label class="pull-right"
  39.                             <a href="#">Forgotten Password?</a> 
  40.                         </label> 
  41.                     </div> 
  42.                     <button type="submit" class="btn btn-success btn-flat m-b-30 m-t-30" style="font-size: 18px;">登录</button> 
  43.                 </form> 
  44.             </div> 
  45.         </div> 
  46.     </div> 
  47. </div> 
  48.  
  49.  
  50. <script type="text/javascript" th:src="@{/assets/js/jquery-2.1.4.min.js}"></script> 
  51. <script type="text/javascript" th:src="@{/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script> 
  52. <script type="text/javascript" th:src="@{/assets/js/main.js}"></script> 
  53.  
  54. </body> 
  55. </html> 

定义客户端,如下图:

加载用户,登录账户:

  1. package com.cjs.sso.domain; 
  2.  
  3. import lombok.Data; 
  4. import org.springframework.security.core.GrantedAuthority; 
  5. import org.springframework.security.core.userdetails.User
  6.  
  7. import java.util.Collection; 
  8.  
  9. /** 
  10.  * 大部分时候直接用User即可不必扩展 
  11.  * @author ChengJianSheng 
  12.  * @date 2019-02-11 
  13.  */ 
  14. @Data 
  15. public class MyUser extends User { 
  16.  
  17.     private Integer departmentId;   //  举个例子,部门ID 
  18.  
  19.     private String mobile;  //  举个例子,假设我们想增加一个字段,这里我们增加一个mobile表示手机号 
  20.  
  21.     public MyUser(String username, String password, Collection<? extends GrantedAuthority> authorities) { 
  22.         super(username, password, authorities); 
  23.     } 
  24.  
  25.     public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { 
  26.         super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); 
  27.     } 

加载登录账户:

  1. package com.cjs.sso.service; 
  2.  
  3. import com.alibaba.fastjson.JSON; 
  4. import com.cjs.sso.domain.MyUser; 
  5. import com.cjs.sso.entity.SysPermission; 
  6. import com.cjs.sso.entity.SysUser; 
  7. import lombok.extern.slf4j.Slf4j; 
  8. import org.springframework.beans.factory.annotation.Autowired; 
  9. import org.springframework.security.core.authority.SimpleGrantedAuthority; 
  10. import org.springframework.security.core.userdetails.UserDetails; 
  11. import org.springframework.security.core.userdetails.UserDetailsService; 
  12. import org.springframework.security.core.userdetails.UsernameNotFoundException; 
  13. import org.springframework.security.crypto.password.PasswordEncoder; 
  14. import org.springframework.stereotype.Service; 
  15. import org.springframework.util.CollectionUtils; 
  16.  
  17. import java.util.ArrayList; 
  18. import java.util.List; 
  19.  
  20. /** 
  21.  * @author ChengJianSheng 
  22.  * @date 2019-02-11 
  23.  */ 
  24. @Slf4j 
  25. @Service 
  26. public class MyUserDetailsService implements UserDetailsService { 
  27.  
  28.     @Autowired 
  29.     private PasswordEncoder passwordEncoder; 
  30.  
  31.     @Autowired 
  32.     private UserService userService; 
  33.  
  34.     @Autowired 
  35.     private PermissionService permissionService; 
  36.  
  37.     @Override 
  38.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
  39.         SysUser sysUser = userService.getByUsername(username); 
  40.         if (null == sysUser) { 
  41.             log.warn("用户{}不存在", username); 
  42.             throw new UsernameNotFoundException(username); 
  43.         } 
  44.         List<SysPermission> permissionList = permissionService.findByUserId(sysUser.getId()); 
  45.         List<SimpleGrantedAuthority> authorityList = new ArrayList<>(); 
  46.         if (!CollectionUtils.isEmpty(permissionList)) { 
  47.             for (SysPermission sysPermission : permissionList) { 
  48.                 authorityList.add(new SimpleGrantedAuthority(sysPermission.getCode())); 
  49.             } 
  50.         } 
  51.  
  52.         MyUser myUser = new MyUser(sysUser.getUsername(), passwordEncoder.encode(sysUser.getPassword()), authorityList); 
  53.  
  54.         log.info("登录成功!用户: {}", JSON.toJSONString(myUser)); 
  55.  
  56.         return myUser; 
  57.     } 

验证:

当我们看到这个界面的时候,表示认证服务器配置完成。

两个客户端

Maven 依赖:

  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 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  4.     <modelVersion>4.0.0</modelVersion> 
  5.     <parent> 
  6.         <groupId>org.springframework.boot</groupId> 
  7.         <artifactId>spring-boot-starter-parent</artifactId> 
  8.         <version>2.1.3.RELEASE</version> 
  9.         <relativePath/> <!-- lookup parent from repository --> 
  10.     </parent> 
  11.     <groupId>com.cjs.sso</groupId> 
  12.     <artifactId>oauth2-sso-client-member</artifactId> 
  13.     <version>0.0.1-SNAPSHOT</version> 
  14.     <name>oauth2-sso-client-member</name
  15.     <description>Demo project for Spring Boot</description> 
  16.  
  17.     <properties> 
  18.         <java.version>1.8</java.version> 
  19.     </properties> 
  20.  
  21.     <dependencies> 
  22.         <dependency> 
  23.             <groupId>org.springframework.boot</groupId> 
  24.             <artifactId>spring-boot-starter-data-jpa</artifactId> 
  25.         </dependency> 
  26.         <dependency> 
  27.             <groupId>org.springframework.boot</groupId> 
  28.             <artifactId>spring-boot-starter-oauth2-client</artifactId> 
  29.         </dependency> 
  30.         <dependency> 
  31.             <groupId>org.springframework.boot</groupId> 
  32.             <artifactId>spring-boot-starter-security</artifactId> 
  33.         </dependency> 
  34.         <dependency> 
  35.             <groupId>org.springframework.security.oauth.boot</groupId> 
  36.             <artifactId>spring-security-oauth2-autoconfigure</artifactId> 
  37.             <version>2.1.3.RELEASE</version> 
  38.         </dependency> 
  39.         <dependency> 
  40.             <groupId>org.springframework.boot</groupId> 
  41.             <artifactId>spring-boot-starter-thymeleaf</artifactId> 
  42.         </dependency> 
  43.         <dependency> 
  44.             <groupId>org.thymeleaf.extras</groupId> 
  45.             <artifactId>thymeleaf-extras-springsecurity5</artifactId> 
  46.             <version>3.0.4.RELEASE</version> 
  47.         </dependency> 
  48.         <dependency> 
  49.             <groupId>org.springframework.boot</groupId> 
  50.             <artifactId>spring-boot-starter-web</artifactId> 
  51.         </dependency> 
  52.  
  53.         <dependency> 
  54.             <groupId>com.h2database</groupId> 
  55.             <artifactId>h2</artifactId> 
  56.             <scope>runtime</scope> 
  57.         </dependency> 
  58.         <dependency> 
  59.             <groupId>org.projectlombok</groupId> 
  60.             <artifactId>lombok</artifactId> 
  61.             <optional>true</optional> 
  62.         </dependency> 
  63.         <dependency> 
  64.             <groupId>org.springframework.boot</groupId> 
  65.             <artifactId>spring-boot-starter-test</artifactId> 
  66.             <scope>test</scope> 
  67.         </dependency> 
  68.         <dependency> 
  69.             <groupId>org.springframework.security</groupId> 
  70.             <artifactId>spring-security-test</artifactId> 
  71.             <scope>test</scope> 
  72.         </dependency> 
  73.     </dependencies> 
  74.  
  75.     <build> 
  76.         <plugins> 
  77.             <plugin> 
  78.                 <groupId>org.springframework.boot</groupId> 
  79.                 <artifactId>spring-boot-maven-plugin</artifactId> 
  80.             </plugin> 
  81.         </plugins> 
  82.     </build> 
  83.  
  84. </project> 

application.yml:

  1. server: 
  2.   port: 8082 
  3.   servlet: 
  4.     context-path: /memberSystem 
  5. security: 
  6.   oauth2: 
  7.     client: 
  8.       client-id: UserManagement 
  9.       client-secret: user123 
  10.       access-token-uri: http://localhost:8080/oauth/token 
  11.       user-authorization-uri: http://localhost:8080/oauth/authorize 
  12.     resource: 
  13.       jwt: 
  14.         key-uri: http://localhost:8080/oauth/token_key 

这里 context-path 不要设成 /,不然重定向获取 code 的时候回被拦截。

WebSecurityConfig:

  1. package com.cjs.example.config; 
  2.  
  3. import com.cjs.example.util.EnvironmentUtils; 
  4. import org.springframework.beans.factory.annotation.Autowired; 
  5. import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; 
  6. import org.springframework.context.annotation.Configuration; 
  7. import org.springframework.security.config.annotation.web.builders.HttpSecurity; 
  8. import org.springframework.security.config.annotation.web.builders.WebSecurity; 
  9. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 
  10.  
  11.  
  12. /** 
  13.  * @author ChengJianSheng 
  14.  * @date 2019-03-03 
  15.  */ 
  16. @EnableOAuth2Sso 
  17. @Configuration 
  18. public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
  19.  
  20.     @Autowired 
  21.     private EnvironmentUtils environmentUtils; 
  22.  
  23.     @Override 
  24.     public void configure(WebSecurity web) throws Exception { 
  25.         web.ignoring().antMatchers("/bootstrap/**"); 
  26.     } 
  27.  
  28.     @Override 
  29.     protected void configure(HttpSecurity http) throws Exception { 
  30.         if ("local".equals(environmentUtils.getActiveProfile())) { 
  31.             http.authorizeRequests().anyRequest().permitAll(); 
  32.         }else { 
  33.             http.logout().logoutSuccessUrl("http://localhost:8080/logout"
  34.                     .and() 
  35.                     .authorizeRequests() 
  36.                     .anyRequest().authenticated() 
  37.                     .and() 
  38.                     .csrf().disable(); 
  39.         } 
  40.     } 

说明:

  • 最重要的注解是 @EnableOAuth2Sso。
  • 权限控制这里采用 Spring Security 方法级别的访问控制,结合 Thymeleaf 可以很容易做权限控制。
  • 顺便多提一句,如果是前后端分离的话,前端需求加载用户的权限,然后判断应该显示那些按钮那些菜单。

MemberController:

  1. package com.cjs.example.controller; 
  2.  
  3. import org.springframework.security.access.prepost.PreAuthorize; 
  4. import org.springframework.security.core.Authentication; 
  5. import org.springframework.stereotype.Controller; 
  6. import org.springframework.web.bind.annotation.GetMapping; 
  7. import org.springframework.web.bind.annotation.PostMapping; 
  8. import org.springframework.web.bind.annotation.RequestMapping; 
  9. import org.springframework.web.bind.annotation.ResponseBody; 
  10.  
  11. import java.security.Principal; 
  12.  
  13. /** 
  14.  * @author ChengJianSheng 
  15.  * @date 2019-03-03 
  16.  */ 
  17. @Controller 
  18. @RequestMapping("/member"
  19. public class MemberController { 
  20.  
  21.     @GetMapping("/list"
  22.     public String list() { 
  23.  
  24.         return "member/list"
  25.     } 
  26.  
  27.     @GetMapping("/info"
  28.     @ResponseBody 
  29.     public Principal info(Principal principal) { 
  30.         return principal; 
  31.     } 
  32.  
  33.     @GetMapping("/me"
  34.     @ResponseBody 
  35.     public Authentication me(Authentication authentication) { 
  36.         return authentication; 
  37.     } 
  38.  
  39.     @PreAuthorize("hasAuthority('member:save')"
  40.     @ResponseBody 
  41.     @PostMapping("/add"
  42.     public String add() { 
  43.  
  44.         return "add"
  45.     } 
  46.  
  47.     @PreAuthorize("hasAuthority('member:detail')"
  48.     @ResponseBody 
  49.     @GetMapping("/detail"
  50.     public String detail() { 
  51.         return "detail"
  52.     } 

Order 项目跟它是一样的:

  1. server: 
  2.   port: 8083 
  3.   servlet: 
  4.     context-path: /orderSystem 
  5. security: 
  6.   oauth2: 
  7.     client: 
  8.       client-id: OrderManagement 
  9.       client-secret: order123 
  10.       access-token-uri: http://localhost:8080/oauth/token 
  11.       user-authorization-uri: http://localhost:8080/oauth/authorize 
  12.     resource: 
  13.       jwt: 
  14.         key-uri: http://localhost:8080/oauth/token_key 

关于退出:退出就是清空用于与 SSO 客户端建立的所有的会话,简单的来说就是使所有端点的 Session 失效,如果想做得更好的话可以令 Token 失效,但是由于我们用的 JWT,故而撤销 Token 就不是那么容易,关于这一点,在官网上也有提到:

本例中采用的方式是在退出的时候先退出业务服务器,成功以后再回调认证服务器,但是这样有一个问题,就是需要主动依次调用各个业务服务器的 logout。

工程结构

附上源码:

  1. https://github.com/chengjiansheng/cjs-oauth2-sso-demo.git 

演示

作者:废物大师兄

编辑:陶家龙

出处:cnblogs.com/cjsblog/p/10548022.html

责任编辑:武晓燕 来源: 博客园
相关推荐

2021-07-29 10:26:34

数据分析上云CIO

2022-03-21 21:05:40

TypeScript语言API

2019-08-08 16:30:23

技术编程SpringBoot

2022-04-27 20:52:48

JSChrome元素

2021-05-14 07:11:49

方法调用类加载

2020-09-15 12:40:16

回溯算法代码回溯法

2021-01-21 00:06:26

vue.js语言开发

2021-09-28 10:48:07

开源双因素认证单点登录

2023-05-12 09:08:48

TypeScript工具类型

2020-04-15 08:33:43

Netty网络通信

2020-09-21 08:33:12

线程池调度Thread Pool

2020-03-27 09:06:54

选择排序算法冒泡排序

2010-09-06 10:15:11

DB2打补丁

2020-12-28 05:52:27

SSO登录单点

2018-09-28 14:28:28

MySQL存储过程

2019-08-14 10:20:32

算法数组链表

2019-08-22 09:22:44

数据结构二叉搜索树

2021-05-13 07:30:27

Kafka消息流系统

2021-07-15 09:55:47

systemdLinux文件

2019-01-15 09:55:24

RAID磁盘阵列数据存储
点赞
收藏

51CTO技术栈公众号