当前位置:   article > 正文

Spring Boot2 使用 Spring Security + OAuth2 实现单点登录SSO_springboot oauth2.0单点登录

springboot oauth2.0单点登录

前言

目前系统都是比较流行的微服务架构,在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,使用人员每天用自己的账号登录,很方便。但随着企业的发展,用到的微服务系统随之增多,使用人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,都要记录,这对于使用人员来说,很不方便还不友好。于是,就想到是不是可以在一个统一登录门户平台登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。
单点登录英文全称Single sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

技术简述

使用当下最流行的SpringBoot2技术,持久层使用MyBatis,权限控制Spring Security,数据库MySql,基于OAuth2认证授权协议,构建一个易理解、高可用、高扩展性的分布式单点登录应用基层。

OAuth2授权方式

OAuth2为我们提供了四种授权方式:

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

授权码模式:授权码相对其他三种来说是功能比较完整、流程最安全严谨的授权方式,通过客户端的后台服务器与服务提供商的认证服务器交互来完成。

简化模式:这种模式不通过服务器端程序来完成,直接由浏览器发送请求获取令牌,令牌是完全暴露在浏览器中的,这种模式不太安全。

密码模式:密码模式也是比较常用到的一种,客户端向授权服务器提供用户名、密码然后得到授权令牌。这种模式不过有种弊端,我们的客户端需要存储用户输入的密码,但是对于用户来说信任度不高的平台是不可能让他们输入密码的。

客户端模式:客户端模式是客户端以自己的名义去授权服务器申请授权令牌,并不是完全意义上的授权。

上述简单的介绍了OAuth2内部的四种授权方式,我们下面使用授权码模式进行微服务单点登录实现,并且我们使用数据库存储用户登录信息、客户端授权信息。

一 授权服务

创建Spring Boot2项目,版本:2.3.0,项目名称:cloud-sso-serve

1.1 pom.xml

  1. <!-- oauth2 -->
  2. <dependency>
  3. <groupId>org.springframework.security.oauth.boot</groupId>
  4. <artifactId>spring-security-oauth2-autoconfigure</artifactId>
  5. </dependency>
  6. <!-- thymeleaf 模板引擎 -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  10. </dependency>
  11. <!-- spring security -->
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-security</artifactId>
  15. </dependency>

1.2 application.properties

  1. server.port=8087
  2. server.servlet.context-path=/sso
  3. #启用优雅关机
  4. server.shutdown=graceful
  5. #缓冲10秒
  6. spring.lifecycle.timeout-per-shutdown-phase=10s
  7. # mysql连接
  8. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  9. spring.datasource.url=jdbc:mysql://localhost:3306/sso?useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8
  10. spring.datasource.username=root
  11. spring.datasource.password=root
  12. # druid web页面
  13. druid.login.enabled=false
  14. druid.login.username=druid
  15. druid.login.password=druid
  16. #druid连接池
  17. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  18. spring.datasource.initialSize=20
  19. spring.datasource.minIdle=30
  20. spring.datasource.maxActive=50
  21. spring.datasource.maxWait=60000
  22. spring.datasource.timeBetweenEvictionRunsMillis=60000
  23. spring.datasource.minEvictableIdleTimeMillis=300000
  24. spring.datasource.validationQuery=SELECT 'x'
  25. spring.datasource.testWhileIdle=true
  26. spring.datasource.testOnBorrow=true
  27. spring.datasource.testOnReturn=true
  28. spring.datasource.poolPreparedStatements=true
  29. spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
  30. spring.datasource.filters=stat
  31. spring.datasource.connectionProperties:druid.stat.slowSqlMillis=5000
  32. # MyBatis 配置
  33. mybatis.mapper-locations=classpath*:mapper/**/*Mapper.xml
  34. # mybatis-plus 配置
  35. mybatis-plus.mapper-locations=classpath*:mapper/**/*Mapper.xml
  36. mybatis.configuration.jdbc-type-for-null=null
  37. # thymeleaf 模板引擎配置
  38. spring.thymeleaf.mode=HTML5
  39. spring.thymeleaf.encoding=UTF-8
  40. spring.thymeleaf.servlet.content-type=text/html
  41. #开发时关闭缓存,不然没法看到实时页面
  42. spring.thymeleaf.cache=false
  43. #页面的存放路径就使用默认配置了
  44. spring.thymeleaf.prefix=classpath:/templates/
  45. spring.thymeleaf.check-template-location=true
  46. spring.thymeleaf.suffix=.html

1.3 数据库表

  1. CREATE TABLE `oauth_client_details` (
  2. `client_id` VARCHAR(256) CHARACTER SET utf8 NOT NULL COMMENT '客户端唯一标识ID',
  3. `resource_ids` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端所能访问的资源id集合',
  4. `client_secret` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端访问密匙',
  5. `scope` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端申请的权限范围',
  6. `authorized_grant_types` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端授权类型',
  7. `web_server_redirect_uri` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端的重定向URI',
  8. `authorities` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端所拥有的权限值',
  9. `access_token_validity` INT(11) DEFAULT NULL COMMENT '客户端access_token的有效时间(单位:秒)',
  10. `refresh_token_validity` INT(11) DEFAULT NULL,
  11. `additional_information` VARCHAR(4096) CHARACTER SET utf8 DEFAULT NULL COMMENT '预留的字段',
  12. `autoapprove` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '是否跳过授权(true是,false否)',
  13. PRIMARY KEY (`client_id`)
  14. ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='客户端授权表'
  15. insert into `oauth_client_details`
  16. (`client_id`,`resource_ids`,`client_secret`,`scope`,`authorized_grant_types`,`web_server_redirect_uri`,`authorities`,`access_token_validity`,`refresh_token_validity`,`additional_information`,`autoapprove`) values
  17. ('client1',NULL,'$2a$10$zLD4yC3sL.n58Fh52EN3C.CKloW6GN3QeJrNPfGaqotaH04M2Ssm6','all','authorization_code,refresh_token','http://localhost:8086/client1/login',NULL,7200,NULL,NULL,'true'),
  18. ('client2',NULL,'$2a$10$zLD4yC3sL.n58Fh52EN3C.CKloW6GN3QeJrNPfGaqotaH04M2Ssm6','all','authorization_code,refresh_token','http://localhost:8085/client2/login',NULL,7200,NULL,NULL,'true');
  19. CREATE TABLE `sys_menu` (
  20. `menu_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  21. `menu_name` VARCHAR(50) NOT NULL COMMENT '菜单名称',
  22. `menu_vice_name` VARCHAR(50) NOT NULL COMMENT '菜单副名称',
  23. `parent_id` BIGINT(20) DEFAULT '0' COMMENT '父菜单ID',
  24. `order_num` INT(4) DEFAULT '0' COMMENT '显示顺序',
  25. `url` VARCHAR(200) DEFAULT '#' COMMENT '请求地址',
  26. `menu_type` CHAR(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
  27. `visible` CHAR(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  28. `perms` VARCHAR(100) DEFAULT NULL COMMENT '权限标识',
  29. `icon` VARCHAR(100) DEFAULT '#' COMMENT '菜单图标',
  30. `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
  31. `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  32. `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
  33. `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
  34. `remark` VARCHAR(500) DEFAULT '' COMMENT '备注',
  35. PRIMARY KEY (`menu_id`)
  36. ) ENGINE=INNODB AUTO_INCREMENT=1091 DEFAULT CHARSET=utf8 COMMENT='菜单权限表'
  37. CREATE TABLE `sys_role` (
  38. `role_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  39. `role_name` VARCHAR(30) NOT NULL COMMENT '角色名称',
  40. `role_key` VARCHAR(100) NOT NULL COMMENT '角色权限字符串',
  41. `role_sort` INT(4) NOT NULL COMMENT '显示顺序',
  42. `status` CHAR(1) NOT NULL COMMENT '角色状态(0正常 1停用 2删除)',
  43. `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
  44. `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  45. `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
  46. `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
  47. `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
  48. PRIMARY KEY (`role_id`)
  49. ) ENGINE=INNODB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8 COMMENT='角色信息表'
  50. CREATE TABLE `sys_role_menu` (
  51. `role_id` BIGINT(20) NOT NULL COMMENT '角色ID',
  52. `menu_id` BIGINT(20) NOT NULL COMMENT '菜单ID',
  53. PRIMARY KEY (`role_id`,`menu_id`)
  54. ) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='角色和菜单关联表'
  55. CREATE TABLE `sys_user` (
  56. `user_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  57. `dept_id` BIGINT(20) DEFAULT NULL COMMENT '部门ID',
  58. `login_name` VARCHAR(30) NOT NULL COMMENT '登录名称',
  59. `user_name` VARCHAR(30) DEFAULT NULL COMMENT '用户名称',
  60. `email` VARCHAR(50) DEFAULT '' COMMENT '用户邮箱',
  61. `phone` VARCHAR(11) DEFAULT '' COMMENT '手机号码',
  62. `telephone` VARCHAR(12) DEFAULT '' COMMENT '座机号码',
  63. `duty` VARCHAR(30) DEFAULT '' COMMENT '职务',
  64. `sex` CHAR(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
  65. `avatar` VARCHAR(100) DEFAULT '' COMMENT '头像路径',
  66. `password` VARCHAR(100) DEFAULT '' COMMENT '密码',
  67. `salt` VARCHAR(20) DEFAULT '' COMMENT '盐加密',
  68. `status` CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用 2删除)',
  69. `login_ip` VARCHAR(50) DEFAULT '' COMMENT '最后登陆IP',
  70. `login_date` DATETIME DEFAULT NULL COMMENT '最后登陆时间',
  71. `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
  72. `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  73. `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
  74. `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
  75. `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
  76. PRIMARY KEY (`user_id`)
  77. ) ENGINE=INNODB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8 COMMENT='用户信息表'
  78. insert into `sys_user`
  79. (`user_id`,`dept_id`,`login_name`,`user_name`,`email`,`phone`,`telephone`,`duty`,`sex`,`avatar`,`password`,`salt`,`status`,`login_ip`,`login_date`,`create_by`,`create_time`,`update_by`,`update_time`,`remark`) values
  80. (1,1,'admin','admin','admin@sina.cn','','','','0','','123','111','0','10.96.217.201','2021-02-25 11:15:51','',NULL,'',NULL,NULL);
  81. CREATE TABLE `sys_user_role` (
  82. `user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
  83. `role_id` BIGINT(20) NOT NULL COMMENT '角色ID',
  84. PRIMARY KEY (`user_id`,`role_id`)
  85. ) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='用户和角色关联表'

1.4 代码实现

1.4.1 AuthorizationServerConfig客户端授权配置

  1. package com.modules.common.config;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
  6. import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
  7. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
  8. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
  9. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
  10. import org.springframework.security.oauth2.provider.token.TokenStore;
  11. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
  12. import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
  13. import javax.sql.DataSource;
  14. /**
  15. * 客户端授权配置
  16. */
  17. @Configuration
  18. @EnableAuthorizationServer // 开启授权服务器
  19. public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  20. @Autowired
  21. private DataSource dataSource;
  22. /**
  23. * 配置第三方应用,可以放在内存(inMemory),数据库
  24. * 四种授权模式("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
  25. * 1、授权码模式(authorization code)(正宗方式)(支持refresh token)
  26. * 2、密码模式(password)(为遗留系统设计)(支持refresh token)
  27. * 3、客户端模式(client_credentials)(为后台api服务消费者设计)(不支持refresh token)
  28. * 4、简化模式(implicit)(为web浏览器应用设计)(不支持refresh token)
  29. *
  30. * @param clients
  31. * @throws Exception
  32. */
  33. @Override
  34. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  35. clients.jdbc(dataSource);
  36. }
  37. /**
  38. * 需要暴露授权服务器端点
  39. *
  40. * @param endpoints
  41. * @throws Exception
  42. */
  43. @Override
  44. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  45. endpoints.accessTokenConverter(jwtAccessTokenConverter());
  46. endpoints.tokenStore(jwtTokenStore());
  47. // endpoints.tokenServices(defaultTokenServices());
  48. }
  49. @Override
  50. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  51. security.allowFormAuthenticationForClients();
  52. security.tokenKeyAccess("isAuthenticated()");
  53. }
  54. /**
  55. * 配置TokenStore,有多种实现方式,redis,jwt,jdbc
  56. * @return
  57. */
  58. @Bean
  59. public TokenStore jwtTokenStore() {
  60. return new JwtTokenStore(jwtAccessTokenConverter());
  61. }
  62. @Bean
  63. public JwtAccessTokenConverter jwtAccessTokenConverter(){
  64. JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
  65. converter.setSigningKey("testKey");
  66. return converter;
  67. }
  68. }

1.4.2 SpringSecurityConfig权限配置

  1. package com.modules.common.config;
  2. import com.modules.system.service.SysLoginService;
  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.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  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.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. * spring security配置
  16. * @author li'chao
  17. */
  18. @Configuration
  19. @EnableWebSecurity
  20. public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
  21. /**
  22. * 自定义用户认证逻辑
  23. */
  24. @Autowired
  25. private SysLoginService sysLoginService;
  26. @Override
  27. protected void configure(HttpSecurity http) throws Exception {
  28. http.formLogin()
  29. .loginPage("/login")
  30. .and()
  31. .authorizeRequests()
  32. .antMatchers("/login").permitAll()
  33. .anyRequest()
  34. .authenticated()
  35. .and().csrf().disable().cors();
  36. /* http
  37. .requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
  38. .and()
  39. .authorizeRequests()
  40. .antMatchers("/oauth/**").authenticated()
  41. .and()
  42. .formLogin().permitAll();*/
  43. }
  44. @Override
  45. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  46. auth.userDetailsService(sysLoginService).passwordEncoder(passwordEncoder());
  47. }
  48. /**
  49. * 强散列哈希加密实现
  50. */
  51. @Bean
  52. public PasswordEncoder passwordEncoder() {
  53. return new BCryptPasswordEncoder();
  54. }
  55. @Override
  56. public void configure(WebSecurity web) throws Exception {
  57. web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
  58. }
  59. }

1.4.3 SysLoginService登录处理业务

  1. package com.modules.system.service;
  2. import com.baomidou.mybatisplus.toolkit.CollectionUtils;
  3. import com.modules.system.entity.SysUser;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  7. import org.springframework.security.core.userdetails.User;
  8. import org.springframework.security.core.userdetails.UserDetails;
  9. import org.springframework.security.core.userdetails.UserDetailsService;
  10. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  11. import org.springframework.security.crypto.password.PasswordEncoder;
  12. import org.springframework.stereotype.Component;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. /**
  16. * 用户登录 业务层
  17. * @author li'chao
  18. *
  19. */
  20. @Slf4j
  21. @Component
  22. public class SysLoginService implements UserDetailsService {
  23. @Autowired
  24. private PasswordEncoder passwordEncoder;
  25. @Autowired
  26. private SysUserService sysUserService;
  27. @Autowired
  28. private SysMenuService sysMenuService;
  29. @Override
  30. public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
  31. // 根据登录名称查询用户信息
  32. SysUser sysUser = sysUserService.selectUserByLoginName(name);
  33. if (null == sysUser) {
  34. log.warn("用户{}不存在", name);
  35. throw new UsernameNotFoundException(name);
  36. }
  37. // 根据用户ID查询权限配置的菜单,获取菜单标识字段perms
  38. List<String> permsList = sysMenuService.selectPermsListByUserId(sysUser.getUserId());
  39. permsList.remove(null);
  40. List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
  41. if(!CollectionUtils.isEmpty(permsList)){
  42. for(String str : permsList){
  43. authorityList.add(new SimpleGrantedAuthority(str));
  44. }
  45. }
  46. return new User(sysUser.getLoginName(), passwordEncoder.encode(sysUser.getPassword()), authorityList);
  47. }
  48. }

1.4.4 LoginController登录处理

  1. package com.modules.system.controller;
  2. import com.modules.common.web.BaseController;
  3. import com.modules.system.service.SysUserService;
  4. import io.swagger.annotations.Api;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
  8. import org.springframework.stereotype.Controller;
  9. import org.springframework.web.bind.annotation.CrossOrigin;
  10. import org.springframework.web.bind.annotation.GetMapping;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpServletResponse;
  14. import java.io.IOException;
  15. /**
  16. * 登录管理
  17. * @author lc
  18. */
  19. @Api(tags = "登录管理")
  20. @Slf4j
  21. @CrossOrigin
  22. @Controller
  23. public class LoginController extends BaseController
  24. {
  25. @Autowired
  26. private SysUserService userService;
  27. /**
  28. * 自定义登录页面
  29. * @return
  30. */
  31. @GetMapping("/login")
  32. public String login() {
  33. return "login";
  34. }
  35. /**
  36. * 登录成功后显示的首页
  37. * @return
  38. */
  39. @GetMapping("/")
  40. public String index() {
  41. return "index";
  42. }
  43. @RequestMapping("oauth/exit")
  44. public void exit(HttpServletRequest request, HttpServletResponse response) {
  45. new SecurityContextLogoutHandler().logout(request, null, null);
  46. try {
  47. System.out.println(request.getHeader("referer"));
  48. response.sendRedirect(request.getHeader("referer"));
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }

1.4.5 自定义登录页面

  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. <link type="text/css" rel="stylesheet" th:href="@{/assets/css/normalize.css}">
  10. <link type="text/css" rel="stylesheet" th:href="@{/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}">
  11. <link type="text/css" rel="stylesheet" th:href="@{/assets/css/font-awesome.min.css}">
  12. <link type="text/css" rel="stylesheet" th:href="@{/assets/css/style.css}">
  13. </head>
  14. <body class="bg-dark">
  15. <script type="text/javascript" th:src="@{/assets/js/jquery-2.1.4.min.js}"></script>
  16. <script type="text/javascript" th:src="@{/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script>
  17. <script type="text/javascript" th:src="@{/assets/js/main.js}"></script>
  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>登录名称</label>
  28. <input type="text" class="form-control" name="username" placeholder="请输入登录名称">
  29. </div>
  30. <div class="form-group">
  31. <label>密码</label>
  32. <input type="password" class="form-control" name="password" placeholder="请输入登录密码">
  33. <span style="color: red" ></span>
  34. </div>
  35. <div class="checkbox">
  36. <label>
  37. <input type="checkbox"> 记住我
  38. </label>
  39. <label class="pull-right">
  40. <a href="#">忘记密码</a>
  41. </label>
  42. </div>
  43. <button type="submit" class="btn btn-success btn-flat m-b-30 m-t-30" style="font-size: 18px;">登录</button>
  44. <p style="color: red" th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}" ></p>
  45. </form>
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. </body>
  51. </html>

1.4.6 登录成功后首页

  1. <!DOCTYPE html>
  2. <html lang="en" >
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  6. <title>综合管理平台</title>
  7. <link rel="stylesheet" href="css/style.css">
  8. </head>
  9. <body>
  10. <div class="container-scroller">
  11. <!-- partial -->
  12. <div class="container-fluid page-body-wrapper">
  13. <div class="main-panel">
  14. <div class="content-wrapper">
  15. <div class="page-header">
  16. <h3 class="page-title">
  17. <span class="page-title-icon bg-gradient-primary text-white mr-2">
  18. <i class="mdi mdi-home"></i>
  19. </span>
  20. 欢迎来到综合管理平台
  21. </h3>
  22. </div>
  23. <div class="row">
  24. <div class="col-md-4 stretch-card grid-margin">
  25. <div class="card bg-gradient-danger card-img-holder text-white">
  26. <div class="card-body">
  27. <img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
  28. <h2 class="mb-5"><a href="http://localhost:8086/client1/list" style="color: white">商品管理系统</a></h2>
  29. </div>
  30. </div>
  31. </div>
  32. <div class="col-md-4 stretch-card grid-margin">
  33. <div class="card bg-gradient-info card-img-holder text-white">
  34. <div class="card-body">
  35. <img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
  36. <h2 class="mb-5"><a href="http://localhost:8083/orderSystem/order/list" style="color: white">订单管理系统</a></h2>
  37. </div>
  38. </div>
  39. </div>
  40. <div class="col-md-4 stretch-card grid-margin">
  41. <div class="card bg-gradient-success card-img-holder text-white">
  42. <div class="card-body">
  43. <img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
  44. <h2 class="mb-5">营销管理系统</h2>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. <div class="row">
  50. <div class="col-md-4 stretch-card grid-margin">
  51. <div class="card bg-gradient-danger card-img-holder text-white">
  52. <div class="card-body">
  53. <img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
  54. <h2 class="mb-5">运营管理系统</h2>
  55. </div>
  56. </div>
  57. </div>
  58. <div class="col-md-4 stretch-card grid-margin">
  59. <div class="card bg-gradient-info card-img-holder text-white">
  60. <div class="card-body">
  61. <img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
  62. <h2 class="mb-5">商户管理系统</h2>
  63. </div>
  64. </div>
  65. </div>
  66. <div class="col-md-4 stretch-card grid-margin">
  67. <div class="card bg-gradient-success card-img-holder text-white">
  68. <div class="card-body">
  69. <img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
  70. <h2 class="mb-5">财务管理系统</h2>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <!-- content-wrapper ends -->
  77. <!-- partial -->
  78. </div>
  79. <!-- main-panel ends -->
  80. </div>
  81. <!-- page-body-wrapper ends -->
  82. </div>
  83. <!-- container-scroller -->
  84. </body>
  85. </html>

1.4.7 测试

登录页面:admin/123

登录成功

至此,授权服务实现完成,此间使用授权码模式实现,客户端信息存入数据库,登录页面进行自定义。

二 客户端服务

创建Spring Boot2项目,版本:2.3.0,项目名称:cloud-goods-client

2.1 pom.xml

  1. <!-- oauth2 -->
  2. <dependency>
  3. <groupId>org.springframework.security.oauth.boot</groupId>
  4. <artifactId>spring-security-oauth2-autoconfigure</artifactId>
  5. </dependency>
  6. <!-- thymeleaf 模板引擎 -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  10. </dependency>
  11. <!-- spring security -->
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-security</artifactId>
  15. </dependency>

2.2 application.yml

  1. server:
  2. port: 8086
  3. servlet:
  4. context-path: /client1
  5. auth-server: http://localhost:8087/sso
  6. security:
  7. oauth2:
  8. client:
  9. client-id: client1
  10. client-secret: 123456
  11. user-authorization-uri: ${auth-server}/oauth/authorize
  12. access-token-uri: ${auth-server}/oauth/token
  13. resource:
  14. jwt:
  15. key-uri: ${auth-server}/oauth/token_key
  16. # thymeleaf 模板引擎配置
  17. spring:
  18. thymeleaf:
  19. mode: HTML5
  20. encoding: utf-8
  21. servlet:
  22. content-type: text/html
  23. # 开发时关闭缓存,不然没法看到实时页面
  24. cache: true
  25. # 页面的存放路径就使用默认配置了
  26. prefix: classpath:/templates/
  27. check-template-location: true
  28. suffix: .html

2.3 代码实现

2.3.1 WebSecurityConfigurer配置

  1. package com.modules.config;
  2. import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  6. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  7. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  8. @Configuration
  9. @EnableWebSecurity
  10. @EnableGlobalMethodSecurity(prePostEnabled = true)
  11. @EnableOAuth2Sso
  12. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
  13. @Override
  14. public void configure(HttpSecurity http) throws Exception {
  15. http.antMatcher("/**").authorizeRequests()
  16. .anyRequest().authenticated();
  17. }
  18. }

2.3.2 TestController 测试

  1. package com.modules.controller;
  2. import org.springframework.security.access.prepost.PreAuthorize;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. @Controller
  6. public class TestController {
  7. @GetMapping("/list")
  8. @PreAuthorize("hasAuthority('ROLE_NORMAL')")
  9. public String list( ) {
  10. return "list";
  11. }
  12. }

2.3.3 自定义模拟列表显示

  1. <!DOCTYPE html>
  2. <html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>商品管理系统</title>
  6. </head>
  7. <body>
  8. <h3><span>商品管理系统</span> | <a th:href="@{/logout}">退出</a> </h3>
  9. <h2>这是一个商品管理列表页面</h2>
  10. </body>
  11. </html>

2.3.4 测试

访问客户端 http://localhost:8086/client1/list 自动跳转授权服务器登录页面 http://localhost:8087/upms/login

输入登录名称和密码,自动跳转到客户端 http://localhost:8086/client1/list

访问服务器 http://localhost:8087/upms/

因为我在商品管理系统添加了超链接 http://localhost:8086/client1/list,点击商品管理系统,自动跳转到客户端

至此,客户端微服务集成单点登录完成,多个客户端服务如此集成即可。

源码地址:https://gitee.com/lichao12/spring-boot2-security-oauth2-jwt

参考:OAuth2实现单点登录SSO - 废物大师兄 - 博客园

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号