程序地带

Spring Boot2 使用 Spring Security + OAuth2 实现单点登录SSO


前言

目前系统都是比较流行的微服务架构,在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,使用人员每天用自己的账号登录,很方便。但随着企业的发展,用到的微服务系统随之增多,使用人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,都要记录,这对于使用人员来说,很不方便还不友好。于是,就想到是不是可以在一个统一登录门户平台登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。 单点登录英文全称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
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<!-- thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.2 application.properties
server.port=8087
server.servlet.context-path=/sso
#启用优雅关机
server.shutdown=graceful
#缓冲10秒
spring.lifecycle.timeout-per-shutdown-phase=10s
# mysql连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/sso?useUnicode=true&characterEncoding=UTF-8&userSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
# druid web页面
druid.login.enabled=false
druid.login.username=druid
druid.login.password=druid
#druid连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=20
spring.datasource.minIdle=30
spring.datasource.maxActive=50
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 'x'
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=true
spring.datasource.testOnReturn=true
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=stat
spring.datasource.connectionProperties:druid.stat.slowSqlMillis=5000
# MyBatis 配置
mybatis.mapper-locations=classpath*:mapper/**/*Mapper.xml
# mybatis-plus 配置
mybatis-plus.mapper-locations=classpath*:mapper/**/*Mapper.xml
mybatis.configuration.jdbc-type-for-null=null
# thymeleaf 模板引擎配置
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
#页面的存放路径就使用默认配置了
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.check-template-location=true
spring.thymeleaf.suffix=.html
1.3 数据库表
CREATE TABLE `oauth_client_details` (
`client_id` VARCHAR(256) CHARACTER SET utf8 NOT NULL COMMENT '客户端唯一标识ID',
`resource_ids` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端所能访问的资源id集合',
`client_secret` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端访问密匙',
`scope` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端申请的权限范围',
`authorized_grant_types` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端授权类型',
`web_server_redirect_uri` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端的重定向URI',
`authorities` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端所拥有的权限值',
`access_token_validity` INT(11) DEFAULT NULL COMMENT '客户端access_token的有效时间(单位:秒)',
`refresh_token_validity` INT(11) DEFAULT NULL,
`additional_information` VARCHAR(4096) CHARACTER SET utf8 DEFAULT NULL COMMENT '预留的字段',
`autoapprove` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '是否跳过授权(true是,false否)',
PRIMARY KEY (`client_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='客户端授权表'
insert into `oauth_client_details`
(`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
('client1',NULL,'$2a$10$zLD4yC3sL.n58Fh52EN3C.CKloW6GN3QeJrNPfGaqotaH04M2Ssm6','all','authorization_code,refresh_token','http://localhost:8086/client1/login',NULL,7200,NULL,NULL,'true'),
('client2',NULL,'$2a$10$zLD4yC3sL.n58Fh52EN3C.CKloW6GN3QeJrNPfGaqotaH04M2Ssm6','all','authorization_code,refresh_token','http://localhost:8085/client2/login',NULL,7200,NULL,NULL,'true');
CREATE TABLE `sys_menu` (
`menu_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` VARCHAR(50) NOT NULL COMMENT '菜单名称',
`menu_vice_name` VARCHAR(50) NOT NULL COMMENT '菜单副名称',
`parent_id` BIGINT(20) DEFAULT '0' COMMENT '父菜单ID',
`order_num` INT(4) DEFAULT '0' COMMENT '显示顺序',
`url` VARCHAR(200) DEFAULT '#' COMMENT '请求地址',
`menu_type` CHAR(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
`visible` CHAR(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`perms` VARCHAR(100) DEFAULT NULL COMMENT '权限标识',
`icon` VARCHAR(100) DEFAULT '#' COMMENT '菜单图标',
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT '' COMMENT '备注',
PRIMARY KEY (`menu_id`)
) ENGINE=INNODB AUTO_INCREMENT=1091 DEFAULT CHARSET=utf8 COMMENT='菜单权限表'
CREATE TABLE `sys_role` (
`role_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` VARCHAR(30) NOT NULL COMMENT '角色名称',
`role_key` VARCHAR(100) NOT NULL COMMENT '角色权限字符串',
`role_sort` INT(4) NOT NULL COMMENT '显示顺序',
`status` CHAR(1) NOT NULL COMMENT '角色状态(0正常 1停用 2删除)',
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`)
) ENGINE=INNODB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8 COMMENT='角色信息表'
CREATE TABLE `sys_role_menu` (
`role_id` BIGINT(20) NOT NULL COMMENT '角色ID',
`menu_id` BIGINT(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='角色和菜单关联表'
CREATE TABLE `sys_user` (
`user_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` BIGINT(20) DEFAULT NULL COMMENT '部门ID',
`login_name` VARCHAR(30) NOT NULL COMMENT '登录名称',
`user_name` VARCHAR(30) DEFAULT NULL COMMENT '用户名称',
`email` VARCHAR(50) DEFAULT '' COMMENT '用户邮箱',
`phone` VARCHAR(11) DEFAULT '' COMMENT '手机号码',
`telephone` VARCHAR(12) DEFAULT '' COMMENT '座机号码',
`duty` VARCHAR(30) DEFAULT '' COMMENT '职务',
`sex` CHAR(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` VARCHAR(100) DEFAULT '' COMMENT '头像路径',
`password` VARCHAR(100) DEFAULT '' COMMENT '密码',
`salt` VARCHAR(20) DEFAULT '' COMMENT '盐加密',
`status` CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用 2删除)',
`login_ip` VARCHAR(50) DEFAULT '' COMMENT '最后登陆IP',
`login_date` DATETIME DEFAULT NULL COMMENT '最后登陆时间',
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=INNODB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8 COMMENT='用户信息表'
insert into `sys_user`
(`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
(1,1,'admin','admin','admin@sina.cn','','','','0','','123','111','0','10.96.217.201','2021-02-25 11:15:51','',NULL,'',NULL,NULL);
CREATE TABLE `sys_user_role` (
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`role_id` BIGINT(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='用户和角色关联表'
1.4 代码实现

1.4.1 AuthorizationServerConfig客户端授权配置


package com.modules.common.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.sql.DataSource;
/**
* 客户端授权配置
*/
@Configuration
@EnableAuthorizationServer // 开启授权服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
/**
* 配置第三方应用,可以放在内存(inMemory),数据库
* 四种授权模式("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
* 1、授权码模式(authorization code)(正宗方式)(支持refresh token)
* 2、密码模式(password)(为遗留系统设计)(支持refresh token)
* 3、客户端模式(client_credentials)(为后台api服务消费者设计)(不支持refresh token)
* 4、简化模式(implicit)(为web浏览器应用设计)(不支持refresh token)
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
/**
* 需要暴露授权服务器端点
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter());
endpoints.tokenStore(jwtTokenStore());
// endpoints.tokenServices(defaultTokenServices());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.tokenKeyAccess("isAuthenticated()");
}
/**
* 配置TokenStore,有多种实现方式,redis,jwt,jdbc
* @return
*/
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("testKey");
return converter;
}
}

1.4.2 SpringSecurityConfig权限配置


package com.modules.common.config;
import com.modules.system.service.SysLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* spring security配置
* @author li'chao
*/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自定义用户认证逻辑
*/
@Autowired
private SysLoginService sysLoginService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login")
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest()
.authenticated()
.and().csrf().disable().cors();
/* http
.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
.and()
.authorizeRequests()
.antMatchers("/oauth/**").authenticated()
.and()
.formLogin().permitAll();*/
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(sysLoginService).passwordEncoder(passwordEncoder());
}
/**
* 强散列哈希加密实现
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
}
}

1.4.3 SysLoginService登录处理业务


package com.modules.system.service;
import com.baomidou.mybatisplus.toolkit.CollectionUtils;
import com.modules.system.entity.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 用户登录 业务层
* @author li'chao
*
*/
@Slf4j
@Component
public class SysLoginService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
// 根据登录名称查询用户信息
SysUser sysUser = sysUserService.selectUserByLoginName(name);
if (null == sysUser) {
log.warn("用户{}不存在", name);
throw new UsernameNotFoundException(name);
}
// 根据用户ID查询权限配置的菜单,获取菜单标识字段perms
List<String> permsList = sysMenuService.selectPermsListByUserId(sysUser.getUserId());
permsList.remove(null);
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
if(!CollectionUtils.isEmpty(permsList)){
for(String str : permsList){
authorityList.add(new SimpleGrantedAuthority(str));
}
}
return new User(sysUser.getLoginName(), passwordEncoder.encode(sysUser.getPassword()), authorityList);
}
}

1.4.4 LoginController登录处理


package com.modules.system.controller;
import com.modules.common.web.BaseController;
import com.modules.system.service.SysUserService;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录管理
* @author lc
*/
@Api(tags = "登录管理")
@Slf4j
@CrossOrigin
@Controller
public class LoginController extends BaseController
{
@Autowired
private SysUserService userService;
/**
* 自定义登录页面
* @return
*/
@GetMapping("/login")
public String login() {
return "login";
}
/**
* 登录成功后显示的首页
* @return
*/
@GetMapping("/")
public String index() {
return "index";
}
@RequestMapping("oauth/exit")
public void exit(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, null, null);
try {
System.out.println(request.getHeader("referer"));
response.sendRedirect(request.getHeader("referer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}

1.4.5 自定义登录页面


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Ela Admin - HTML5 Admin Template</title>
<meta name="description" content="Ela Admin - HTML5 Admin Template">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" rel="stylesheet" th:href="@{/assets/css/normalize.css}">
<link type="text/css" rel="stylesheet" th:href="@{/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}">
<link type="text/css" rel="stylesheet" th:href="@{/assets/css/font-awesome.min.css}">
<link type="text/css" rel="stylesheet" th:href="@{/assets/css/style.css}">
</head>
<body class="bg-dark">
<script type="text/javascript" th:src="@{/assets/js/jquery-2.1.4.min.js}"></script>
<script type="text/javascript" th:src="@{/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{/assets/js/main.js}"></script>
<div class="sufee-login d-flex align-content-center flex-wrap">
<div class="container">
<div class="login-content">
<div class="login-logo">
<h1 style="color: #57bf95;">统一登录综合管理平台</h1>
</div>
<div class="login-form">
<form th:action="@{/login}" method="post">
<div class="form-group">
<label>登录名称</label>
<input type="text" class="form-control" name="username" placeholder="请输入登录名称">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" class="form-control" name="password" placeholder="请输入登录密码">
<span style="color: red" ></span>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> 记住我
</label>
<label class="pull-right">
<a href="#">忘记密码</a>
</label>
</div>
<button type="submit" class="btn btn-success btn-flat m-b-30 m-t-30" style="font-size: 18px;">登录</button>
<p style="color: red" th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}" ></p>
</form>
</div>
</div>
</div>
</div>
</body>
</html>

1.4.6 登录成功后首页


<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>综合管理平台</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container-scroller">
<!-- partial -->
<div class="container-fluid page-body-wrapper">
<div class="main-panel">
<div class="content-wrapper">
<div class="page-header">
<h3 class="page-title">
<span class="page-title-icon bg-gradient-primary text-white mr-2">
<i class="mdi mdi-home"></i>
</span>
欢迎来到综合管理平台
</h3>
</div>
<div class="row">
<div class="col-md-4 stretch-card grid-margin">
<div class="card bg-gradient-danger card-img-holder text-white">
<div class="card-body">
<img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
<h2 class="mb-5"><a href="http://localhost:8086/client1/list" style="color: white">商品管理系统</a></h2>
</div>
</div>
</div>
<div class="col-md-4 stretch-card grid-margin">
<div class="card bg-gradient-info card-img-holder text-white">
<div class="card-body">
<img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
<h2 class="mb-5"><a href="http://localhost:8083/orderSystem/order/list" style="color: white">订单管理系统</a></h2>
</div>
</div>
</div>
<div class="col-md-4 stretch-card grid-margin">
<div class="card bg-gradient-success card-img-holder text-white">
<div class="card-body">
<img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
<h2 class="mb-5">营销管理系统</h2>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 stretch-card grid-margin">
<div class="card bg-gradient-danger card-img-holder text-white">
<div class="card-body">
<img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
<h2 class="mb-5">运营管理系统</h2>
</div>
</div>
</div>
<div class="col-md-4 stretch-card grid-margin">
<div class="card bg-gradient-info card-img-holder text-white">
<div class="card-body">
<img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
<h2 class="mb-5">商户管理系统</h2>
</div>
</div>
</div>
<div class="col-md-4 stretch-card grid-margin">
<div class="card bg-gradient-success card-img-holder text-white">
<div class="card-body">
<img src="images/dashboard/circle.svg" class="card-img-absolute" alt="circle-image"/>
<h2 class="mb-5">财务管理系统</h2>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
<!-- partial -->
</div>
<!-- main-panel ends -->
</div>
<!-- page-body-wrapper ends -->
</div>
<!-- container-scroller -->
</body>
</html>

1.4.7 测试


登录页面:admin/123



登录成功



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


二 客户端服务

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



2.1 pom.xml
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<!-- thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.2 application.yml
server:
port: 8086
servlet:
context-path: /client1
auth-server: http://localhost:8087/sso
security:
oauth2:
client:
client-id: client1
client-secret: 123456
user-authorization-uri: ${auth-server}/oauth/authorize
access-token-uri: ${auth-server}/oauth/token
resource:
jwt:
key-uri: ${auth-server}/oauth/token_key
# thymeleaf 模板引擎配置
spring:
thymeleaf:
mode: HTML5
encoding: utf-8
servlet:
content-type: text/html
# 开发时关闭缓存,不然没法看到实时页面
cache: true
# 页面的存放路径就使用默认配置了
prefix: classpath:/templates/
check-template-location: true
suffix: .html
2.3 代码实现

2.3.1 WebSecurityConfigurer配置


package com.modules.config;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableOAuth2Sso
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.anyRequest().authenticated();
}
}

2.3.2 TestController 测试


package com.modules.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class TestController {
@GetMapping("/list")
@PreAuthorize("hasAuthority('ROLE_NORMAL')")
public String list( ) {
return "list";
}
}

2.3.3 自定义模拟列表显示


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


 


参考:https://www.cnblogs.com/cjsblog/p/10548022.html


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lovelichao12/article/details/115195806

随机推荐

es6删除数组某项_十个超级实用的 ES6 特性

 作者:炮哥来源:前端开发社区展开操作符顾名思义,用于对象或数组之前的展开操作符(…),将一个结构展开为列表。演示一下:letfirstHalf=[&...

weixin_39624816 阅读(896)

简单的小题集(一)

文章目录一、请设计一个学生类,公有(public)成员变量包含:姓名、三门课程的成绩(数组)分别对应:...

Magic171 阅读(948)

基于微信小程序的美食商城购物

结果图:微信公众号新白者回复美食商城购物就可以得到源码模块的分析首页模块的分析:在首页中,主要涉及分为四块,分别为第一部分的轮换视图࿰...

新白者 阅读(993)

1.springboot整合springsecutity入门案例

springboot整合springsecutity入门案例一、配置文件实现二、配置类实现三、自定义实现类(查询数据库)1.创建maven项目,引入maven...

一位不爱熬夜但又经常熬夜的程序员 阅读(630)

r语言pls分析_R语言中的偏最小二乘PLS回归算法

偏最小二乘回归:我将围绕结构方程建模(SEM)技术进行一些咨询,以解决独特的业务问题。我们试图识别客户对各种产品的偏好,传统的回归是不够的,因为...

weixin_39885803 阅读(830)