注意:
这篇文章我打算先搁亿搁,
我先完成:Spring Security OAuth2.0 认证协议
然后再开启这篇文章,并且打算用 springsecurity 把 shiro 和cas 给换掉
实战项目:电商管理系统(Element-UI)Vue前端
Github:后端项目
Github:前台项目
视频: https://www.bilibili.com/video/av90846070?p=1
初衷
为前端做支持
(所以比较简单)
根据不同的应用场景,电商系统一般都提供了 PC
端、移动 APP
、 移动 Web
、微信小程序等多种终端访问方式。
Github - 下载整个 - 后端项目
or
单独下载 SQL 脚本
# 登录
mysql -uroot -proot –-default-character-set=utf8
# 创建 vsdb 数据库(vue-shop database) utf8编码
create database vsdb charset utf8 ;
# 进入数据库
use vsdb ;
# 导入数据 数据在github的v-shop-server\db目录下
source F:\environment\java\workspace\v-shop-server\db\vsdb.sql ;
Github - 下载整个 - 后端项目
or
单独下载 api文档
添加 Mybatis-Plus 依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.2.0version>
dependency>
application.yml
server:
port: 3001
servlet:
context-path: /api/private/v1
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/vsdb?useUnicode=true&characterEncoding=utf-8
username: root
password: root
修改 host
# 数据库 192.168.64.33 vdb.cn
HelloController.java
package cn.shop.controller;
import java.util.Date;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/hello")
@RestController
public class HelloController {
@RequestMapping("/now")
public Date showTime() {
return new Date() ;
}
}
http://localhost:3001/api/private/v1/hello/now
Github - 下载整个 - 后端项目
引入三个工具类:CookieUtil、JsonUtil、JsonResult
根据下面状态码,进行相应修改(挺多修改的哦,建议下载来对一下,太多,就不贴上来)
创建测试接口
package cn.shop.controller;
@RequestMapping("/hello")
@RestController
public class HelloController {
@RequestMapping("/cors")
public JsonResult corsJsonResult() {
return JsonResult.ok("cors success!");
}
}
实现 CORS 跨域支持
创建 CorsConfiger 类
package cn.shop.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfiger {
private CorsConfiguration corsConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
/*
* 请求常用的三种配置,*代表允许所有,当然你也可以自定义属性(比如header只能带什么,只能是post方式等等)
*/
// 允许访问的客户端域名
corsConfiguration.addAllowedOrigin("*");
// 允许任何请求头 可选: "x-requested-with,X-Nideshop-Token,X-URL-PATH"
corsConfiguration.addAllowedHeader("*");
// 允许任何方法 可选: "POST, GET, OPTIONS, DELETE"
corsConfiguration.addAllowedMethod("*");
// 允许请求带有验证信息
corsConfiguration.setAllowCredentials(true);
// maxAge(3600)表明在3600秒内,不需要再发送预检验请求,可以缓存该结果
corsConfiguration.setMaxAge(3600L);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 拦截本域名下的所有请求
source.registerCorsConfiguration("/**", corsConfig()); // 设置头配置信息
return new CorsFilter(source);
}
}
测试
localhost:8080
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:3001/api/private/v1/hello/cors');
xhr.send(null);
xhr.onload = function(e) {
var xhr = e.target;
console.log(xhr.responseText);
}
host
192.168.64.33 vdb.cn
端口 3306
对应数据库中的,manager(系统管理员)
加密策略
(shiro、simplehash 加密)
加密算法 : MD5
加密次数:3
密文长度:32
账号 | 密码 | 密文 | 颜值 |
---|---|---|---|
admin | admin | 4e06b3cee0dc656695020bf34e07220f | d5a76533-c05d-49e5-8653-6bc1397dc6ce |
linken | javacas | 68a3f5b307f4ca10750993ffdc3bf1a6 | b83b2044-00b5-4525-92a2-89ce8eb2fb79 |
asdf1 | asdf1 | 116e8a4f2553aa7c23b866c23f93b975 | 7e3c19c2-5a29-4a0f-8956-60f55bc24b58 |
asdf123 | asdf123 | b81c3a9a920620d2e9e2718e963947bc | 40fb1593-4b85-4c1e-88b1-5375c172167b |
根据数据库信息,创建 manager 类
package cn.shop.model.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
@Accessors(chain = true)
@Data
@TableName("sp_manager")
public class Manager {
/**
* 主键 id
*/
@TableId(type = IdType.AUTO, value = "mg_id")
private Integer id;
/**
* 名称
*/
@NotEmpty(message = "用户名不能为空")
@TableField(value = "mg_name")
private String name;
/**
* 密码
*/
@NotEmpty(message = "密码不能为空")
@TableField(value = "mg_pwd")
private String password;
/**
* 盐值
*/
@NotEmpty(message = "盐值异常")
@TableField(value = "mg_salt")
private String salt;
/**
* 注册时间
*/
@TableField(value = "mg_time")
private Long createTime;
/**
* 角色 id
*/
@TableField(value = "role_id")
private Integer roleId;
@TableField(value = "mg_mobile")
private String mobile;
@TableField(value = "mg_email")
private String email;
/**
* 0:启用 1:过期
*/
@TableField("mg_expired")
private Integer expired;
/**
* 0:启用 1:禁用
*/
@TableField("mg_disabled")
private Integer disabled;
}
ManagerMapper
@Mapper
public interface ManagerMapper extends BaseMapper<Manager>{
}
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://vdb.cn:3306/vsdb?useUnicode=true&characterEncoding=utf-8
username: root
password: root
cas 服务端
v-cas
https://cas.cn:8443/cas.
修改 host# Cas Server 127.0.0.1 cas.cn # 后期改为 192.168.64.10
端口 8443
弄这个目的,是为了获取真实的 token 和 实现 SSO。
不想弄的话,可以用静态TOKEN代替
.
.
下图为,整个 架构图 和 官方整理的 cas 工作流程图
.
架构图
cas 工作流程图
CAS 服务端 - 可调试环境快速搭建
github https://github.com/LawssssCat/v-cas
cas 脚手架: https://casinitializr.herokuapp.com/
各个版本模板: https://github.com/apereo/cas-overlay-template
添加 host
127.0.0.1 vshop.cn
端口: 3001
https://blog.csdn.net/catoop/article/details/50534006
https://www.jianshu.com/p/3c6680daab0e
添加依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.2.4version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-casartifactId>
<version>1.2.4version>
dependency>
<dependency>
<groupId>org.jasig.cas.clientgroupId>
<artifactId>cas-client-coreartifactId>
<version>3.2.1version>
dependency>
编写 ShiroCasRealm
doGetAuthorizationInfo()
暂时不写。package cn.shop.common.config.shiro;
import cn.shop.model.po.Manager;
import cn.shop.service.ManagerService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.CollectionUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* @author lawsssscat
*/
public class ShiroCasRealm extends CasRealm {
@Autowired
private ManagerService managerService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 授权,暂时不写
return super.doGetAuthorizationInfo(principals);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
CasToken casToken = (CasToken) token;
// token 为空直接返回,页面会重定向到 Cas Server 页面,并且携带本项目回调页
// 如: https://cas.cn:8443/cas/login?service=http://vshop.cn/manager
if (ObjectUtils.isEmpty(token)) {
return null;
}
// 获取服务端返回的票根
String ticket = (String) casToken.getCredentials();
// 票根为空直接返回,页面会重定向到 Cas Server 页面,并且携带本项目回调页
// 如: https://cas.cn:8443/cas/login?service=http://vshop.cn/manager
if (StringUtils.isEmpty(ticket)) {
return null;
}
// 票根验证器
TicketValidator ticketValidator = ensureTicketValidator();
try {
// 票根验证,会将 ticket 再次发送给 Cas Server ,Cas Server 验证 ticket 的有效性并返回判断 Assertion (返回包含用户数据)
Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
// 获取服务端返回的用户数据
AttributePrincipal casPrincipal = casAssertion.getPrincipal();
// 拿到用户唯一标识(这个标识是 Cas Server 传过来的,我们规定为 id 信息)
String userId = casPrincipal.getName();
Integer id = Integer.parseInt(userId);
// 通过唯一标识查询数据库用户表
// 如果查询到对应用户,则直接返回用户数据
// 如果没有查询到用户数据,则向数据库新增用户并返回用户数据
Manager manager = managerService.findById(id);
// 将获取到的本项目数据库用户包装为 shiro 自身的 principal 存于当前 session 中
// 之后再整个项目中都可以通过 SecurityUtils.getSubject().getPrincipal() 直接去到当前用户信息
List<Object> principals = CollectionUtils.asList(manager, casPrincipal.getAttributes());
SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
return new SimpleAuthenticationInfo(principalCollection, ticket);
} catch (Exception e) {
throw new CasAuthenticationException("Unable to validate ticket {" + ticket + "}", e);
}
}
}
然后是 配置 ShiroManager ,让 shrio 动起来
编写 Shiro 配置类 (必须!)
在配置类中,调用自定义 Realm
创建 casSubjectFactory 是默认的工厂类
(这里,喜欢可以加 shiroCacheManager 缓存管理器,参考 https://www.jianshu.com/p/3c6680daab0e)
创建 securityManager 默认的安全管理器
并且,将 realm、subjectFactory 配置进 manager
最后,过滤器
登出过滤器
logoutFilter
是 shiro 官方实现的 CAS 登出规则过滤器,只需要调用并填写重定向的回调地址即可
redirectUrl
表示用户在本项目中执行登出操作后,会重定向到 CAS Server 的登出页,同时携带再次登录成功后的本项目登录页
通用资源过滤器
filters 中分别指定了 logoutFilter 和 casFilter 映射的别名,会在后续请求映射规则中中使用
首次登陆
已经登录
Bad Request (如下图)
原因:cas 默认 https 访问的。
(TLS是https的一种加密算法)
解决:去 cas 服务器开启 http 访问
修改 cas Server 中的:
- application.properties:
server.ssl.enabled=false
vshop.json
:"serviceId" : "^(https|imaps|http)://vshop.*",
或者:shiro 作为 cas 客户端,弄个 cas 服务器中认证的证书
shiro 官方教程 - http://shiro.apache.org/spring.html
我的 shrio 笔记【⭐️⭐️⭐️】 - https://blog.csdn.net/LawssssCat/article/details/104027123
添加shiro依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.1version>
dependency>