Spring Security基于JWT实现SSO单点登录详解

作者:ZhuPengWei_ 时间:2022-02-28 10:56:55 

SSO :同一个帐号在同一个公司不同系统上登陆

Spring Security基于JWT实现SSO单点登录详解 

使用SpringSecurity实现类似于SSO登陆系统是十分简单的 下面我就搭建一个DEMO

首先来看看目录的结构

Spring Security基于JWT实现SSO单点登录详解 

其中sso-demo是父工程项目 sso-client 、sso-client2分别对应2个资源服务器,sso-server是认证服务器

引入的pom文件

sso-demo


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

<groupId>study.security.sso</groupId>
 <artifactId>sso-demo</artifactId>
 <version>1.0.0-SNAPSHOT</version>
 <modules>
   <module>sso-server</module>
   <module>sso-client</module>
   <module>sso-client2</module>
 </modules>
 <packaging>pom</packaging>

<dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>io.spring.platform</groupId>
       <artifactId>platform-bom</artifactId>
       <version>Brussels-SR4</version>
       <type>pom</type>
       <scope>import</scope>
     </dependency>
     <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-dependencies</artifactId>
       <version>Dalston.SR2</version>
       <type>pom</type>
       <scope>import</scope>
     </dependency>
   </dependencies>
 </dependencyManagement>

<build>
   <plugins>
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <version>2.3.2</version>
       <configuration>
         <source>1.8</source>
         <target>1.8</target>
         <encoding>UTF-8</encoding>
       </configuration>
     </plugin>
   </plugins>
 </build>

</project>

sso-server


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <parent>
   <artifactId>sso-demo</artifactId>
   <groupId>study.security.sso</groupId>
   <version>1.0.0-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>

<artifactId>sso-server</artifactId>

<dependencies>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.security.oauth</groupId>
     <artifactId>spring-security-oauth2</artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-jwt</artifactId>
   </dependency>

</dependencies>

</project>

sso-client与sso-client2 pom 中的 是一样的

1.sso-server

现在开始搭建认证服务器

认证服务器的目录结构如下

Spring Security基于JWT实现SSO单点登录详解


/**
* 认证服务器配置
* Created by ZhuPengWei on 2018/1/11.
*/
@Configuration
@EnableAuthorizationServer
public class SsoAuthenticationServerConfig extends AuthorizationServerConfigurerAdapter {

@Override
 public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   clients.inMemory()
       .withClient("client1")
       .secret("client1")
       .authorizedGrantTypes("authorization_code", "refresh_token")
       .scopes("all")
       .and()
       .withClient("client2")
       .secret("client2")
       .authorizedGrantTypes("authorization_code", "refresh_token")
       .scopes("all");
 }

@Override
 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
 }

/**
  * 认证服务器的安全配置
  *
  * @param security
  * @throws Exception
  */
 @Override
 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
   // 要访问认证服务器tokenKey的时候需要经过身份认证
   security.tokenKeyAccess("isAuthenticated()");
 }

@Bean
 public TokenStore jwtTokenStore() {
   return new JwtTokenStore(jwtAccessTokenConverter());
 }

@Bean
 public JwtAccessTokenConverter jwtAccessTokenConverter() {
   JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
   // 保证JWT安全的唯一方式
   jwtAccessTokenConverter.setSigningKey("ZPW");
   return jwtAccessTokenConverter;
 }
}

/**
* 自定义用户登陆逻辑配置
* Created by ZhuPengWei on 2018/1/13.
*/
@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
 private UserDetailsService userDetailsService;

/**
  * 加密解密逻辑
  */
 @Bean
 public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
 }

@Override
 protected void configure(HttpSecurity http) throws Exception {
   // 改成表单登陆的方式 所有请求都需要认证
   http.formLogin().and().authorizeRequests().anyRequest().authenticated();
 }

@Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   // 用自己的登陆逻辑以及加密器
   auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
 }
}

/**
* 自定义用户登陆
* Created by ZhuPengWei on 2018/1/13.
*/
@Component
public class SsoUserDetailsService implements UserDetailsService {

@Autowired
 private PasswordEncoder passwordEncoder;

@Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

return new User(username,
       passwordEncoder.encode("123456"),
       AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
 }
}

其中SsoApprovalEndPoint与SsoSpelView目的是去掉登陆之后授权的效果

注解 @FrameworkEndpoint

与@RestController注解相类似

如果声明和@FrameworkEndpoint一模一样的@RequestMapping

Spring框架处理的时候会优先处理@RestController里面的

Spring Security基于JWT实现SSO单点登录详解


/**
* 自定义认证逻辑
* Created by ZhuPengWei on 2018/1/13.
*/
@RestController
@SessionAttributes("authorizationRequest")
public class SsoApprovalEndpoint {

@RequestMapping("/oauth/confirm_access")
 public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
   String template = createTemplate(model, request);
   if (request.getAttribute("_csrf") != null) {
     model.put("_csrf", request.getAttribute("_csrf"));
   }
   return new ModelAndView(new SsoSpelView(template), model);
 }

protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
   String template = TEMPLATE;
   if (model.containsKey("scopes") || request.getAttribute("scopes") != null) {
     template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%", "");
   } else {
     template = template.replace("%scopes%", "").replace("%denial%", DENIAL);
   }
   if (model.containsKey("_csrf") || request.getAttribute("_csrf") != null) {
     template = template.replace("%csrf%", CSRF);
   } else {
     template = template.replace("%csrf%", "");
   }
   return template;
 }

private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) {
   StringBuilder builder = new StringBuilder("<ul>");
   @SuppressWarnings("unchecked")
   Map<String, String> scopes = (Map<String, String>) (model.containsKey("scopes") ? model.get("scopes") : request
       .getAttribute("scopes"));
   for (String scope : scopes.keySet()) {
     String approved = "true".equals(scopes.get(scope)) ? " checked" : "";
     String denied = !"true".equals(scopes.get(scope)) ? " checked" : "";
     String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved)
         .replace("%denied%", denied);
     builder.append(value);
   }
   builder.append("</ul>");
   return builder.toString();
 }

private static String CSRF = "<input type='hidden' name='${_csrf.parameterName}' value='${_csrf.token}' />";

private static String DENIAL = "<form id='denialForm' name='denialForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='false' type='hidden'/>%csrf%<label><input name='deny' value='Deny' type='submit'/></label></form>";

// 对源代码进行处理 隐藏授权页面,并且使他自动提交
 private static String TEMPLATE = "<html><body><div style='display:none;'> <h1>OAuth Approval</h1>"
     + "<p>Do you authorize '${authorizationRequest.clientId}' to access your protected resources?</p>"
     + "<form id='confirmationForm' name='confirmationForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='true' type='hidden'/>%csrf%%scopes%<label><input name='authorize' value='Authorize' type='submit'/></label></form>"
     + "%denial%</div><script>document.getElementById('confirmationForm').submit();</script></body></html>";

private static String SCOPE = "<li><div class='form-group'>%scope%: <input type='radio' name='%key%'"
     + " value='true'%approved%>Approve</input> <input type='radio' name='%key%' value='false'%denied%>Deny</input></div></li>";

}

SsoSpelView 与 原来SpelView 是一样的 只不过原来SpelView 不是public的类

application.properties


server.port=9999
server.context-path=/server

2.sso-client

相对于认证服务器 资源服务器demo的配置就十分简单了

Spring Security基于JWT实现SSO单点登录详解


/**
* Created by ZhuPengWei on 2018/1/11.
*/
@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient1Application {

@GetMapping("/user")
 public Authentication user(Authentication user) {
   return user;
 }

public static void main(String[] args) {
   SpringApplication.run(SsoClient1Application.class, args);
 }
}

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>SSO Client1</title>
</head>
<body>
<h1>SSO Demo Client1</h1>
<a href="http://127.0.0.1:8060/client2/index.html" rel="external nofollow" >访问client2</a>

</body>
</html>

application.properties


security.oauth2.client.client-id=client1
security.oauth2.client.client-secret=client1
#需要认证时候跳转的地址
security.oauth2.client.user-authorization-uri=http://127.0.0.1:9999/server/oauth/authorize
#请求令牌地址
security.oauth2.client.access-token-uri=http://127.0.0.1:9999/server/oauth/token
#解析
security.oauth2.resource.jwt.key-uri=http://127.0.0.1:9999/server/oauth/token_key

#sso
server.port=8080
server.context-path=/client1

3.sso-client2

资源服务器1和资源服务器2的目录结构是一样的,改了相关的参数


/**
* Created by ZhuPengWei on 2018/1/11.
*/
@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient2Application {

@GetMapping("/user")
 public Authentication user(Authentication user) {
   return user;
 }

public static void main(String[] args) {
   SpringApplication.run(SsoClient2Application.class, args);
 }
}

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>SSO Client2</title>
</head>
<body>
<h1>SSO Demo Client2</h1>
<a href="http://127.0.0.1:8080/client1/index.html" rel="external nofollow" >访问client1</a>

</body>
</html>

security.oauth2.client.client-id=client2
security.oauth2.client.client-secret=client2
#需要认证时候跳转的地址
security.oauth2.client.user-authorization-uri=http://127.0.0.1:9999/server/oauth/authorize
#请求令牌地址
security.oauth2.client.access-token-uri=http://127.0.0.1:9999/server/oauth/token
#解析
security.oauth2.resource.jwt.key-uri=http://127.0.0.1:9999/server/oauth/token_key

#sso
server.port=8060
server.context-path=/client2

好了 基于JWT实现SSO单点登录的DEMO以及搭建完成了 下面来看看页面的效果

在初次访问的时候

图1

Spring Security基于JWT实现SSO单点登录详解

登陆成功之后

图2

Spring Security基于JWT实现SSO单点登录详解

图3

Spring Security基于JWT实现SSO单点登录详解

注意

写SsoApprovalEndPoint与SsoSpelView目的是去掉登陆之后授权的效果如果不写这2个类

在初次访问的登陆成功之后是有一步授权的操作的

比如说图1操作成功之后

Spring Security基于JWT实现SSO单点登录详解 

点击Authorize才会跳转到图2

来源:https://blog.csdn.net/qq_36144258/article/details/79425942

标签:Spring,Security,JWT,SSO,单点登录
0
投稿

猜你喜欢

  • Java中ArrayList集合的常用方法大全

    2023-09-01 15:23:30
  • Java注解Annotation原理及自定义注解代码实例

    2023-04-30 10:18:09
  • Springboot如何利用拦截器拦截请求信息收集到日志详解

    2023-03-09 02:30:57
  • java实现酒店管理系统

    2023-06-09 23:59:50
  • Spring Boot 2.5.0 重新设计的spring.sql.init 配置有啥用

    2021-12-04 00:04:43
  • 快速学习六大排序算法

    2023-11-02 22:36:19
  • java生成指定范围随机数的多种代码

    2023-04-28 07:51:15
  • java 中Map详解及实例代码

    2023-06-06 01:27:37
  • Java 客户端操作 FastDFS 实现文件上传下载替换删除功能

    2022-06-01 15:01:38
  • 读取xml文件中的配置参数实例

    2023-10-16 16:20:41
  • Spring boot中@Conditional和spring boot的自动配置实例详解

    2023-06-20 09:36:14
  • java模拟TCP通信实现客户端上传文件到服务器端

    2023-11-26 10:14:49
  • java比较器comparator使用示例分享

    2022-07-18 22:45:15
  • Java调用接口如何获取json数据解析后保存到数据库

    2023-11-16 15:01:36
  • Java多线程 两阶段终止模式Two-Phase Termination Patter

    2023-11-29 04:47:04
  • java Semaphore共享锁实现原理解析

    2021-11-02 23:12:38
  • Spring框架应用的权限控制系统详解

    2023-11-11 14:17:11
  • List调用toString()方法后,去除两头的中括号实例

    2023-09-28 11:18:56
  • Struts2中Action中是否需要实现Execute方法

    2021-10-30 06:57:23
  • Android消息机制Handler的工作过程详解

    2023-07-31 13:49:03
  • asp之家 软件编程 m.aspxhome.com