SpringBoot 2.x开发案例分离认证前后

回弹2.x开发案例之前后端分离鉴权

前言

阅读本文需要一定的前后端开发基础,前后端分离已成为互联网项目开发的业界标准使用方式,通过引擎引擎代理雄猫的方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如用户用户:浏览器,小程序,安卓监督办等等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。

其核心思想是前端页面通过AJAX调用后端的空气污染指数接口并使用JSON数据进行交互。

原始模式

开发者通常使用Servlet、Jsp、Velocity、Freemaker、百里香叶以及各种框架模板标签的方式实现前端效果展示。通病就是,后端开发者从后端撸到前端,前端只负责切切页面,修修图,更有甚者,一些团队都没有所谓的前端。

分离模式

在传统架构模式中,前后端代码存放于同一个代码库中,甚至是同一工程目录下。页面中还夹杂着后端代码。前后端分离以后,前后端分成了两个不同的代码库,通常使用Vue、React、Angular、Layui等一系列前端框架实现。

权限校验

回到文章的主题,这里我们使用目前最流行的跨域认证解决方案JSON网络令牌(缩写JWT)

pom.xml引入用户用户:

groupIdio.jsonwebtoken/groupId

artifactIdjjwt/artifactId

版本0 .9 .1/版本

工具类,签发JWT,可以存储简单的用户基础信息,比如用户身份证、用户名等等,只要能识别用户信息即可,重要的角色权限不建议存储用户用户:

/**

JWT加密和解密的工具类

*/公共类JwtUtils {

/**

*加密字符串禁泄漏

*/

公共静态最终字符串秘密= ' E3 F4 E0 ffc 5 e 04432 a 63730 a 65 f 0792 b 00 ';

公共静态最终整数JWT _错误_代码_空=4000;//令牌不存在

公共静态最终JWT国际码=4001;//令牌过期

公共静态最终整数JWT _错误_代码_失败=4002;//验证不通过

/**

*签发JWT

* @param id

* @param主题

* @param ttlMillis

* @返回字符串

*/

公共静态字符串创建(字符串id,字符串主题,长字符串){

签名真实算法HS256 .

现在长毫=系统。CurrentiMemillis();

现在日期=新日期(现在毫秒);

secrettkey secrettkey=常规键();

builder=Jwts.builder()。setId(id)。设置主题(主题)//主题。设置发行者( ' 爪哇笔记' ) //签发者。设置发布日期(现在)//签发时间。签名方式(签名真实算法,秘密密钥);//签名算法以及密匙

if (ttlMillis=0) {

long Expmillis=nowMillis TTlmillis;

日期导出日期=新日期(导出日期);

建筑商。设置过期(ExPDate);//过期时间

}

返回生成器。compact();

}

/**

*验证JWT

* @param jwtStr

* @返回检查结果

*/

公共静态检查结果有效字符串

检查结果检查结果=新检查结果();

索赔要求;

尝试{

claims=ParseJWT(JWTstr);

成功(真);

集合声明(声明);

}捕获(ExpiredJwtException e) {

设置错误代码(JWT错误代码过期);

检查结果。setSuccess(false);

}捕捉(签名例外e) {

设置错误代码(JWT错误代码失败);

检查结果。setSuccess(false);

捕捉(例外e) {

设置错误代码(JWT错误代码失败);

检查结果。setSuccess(false);

}

返回检查结果;

}

/**

*密钥

* @返回

*/

公共静态secrettkey GeneralKey(){

字节[]编码密钥=Base64。解码(保密);

加密密钥=新加密密钥规范(加密密钥,0,加密密钥。长度, ' AES ');

返回键;

}

/**

*解析JWT字符串

* @param jwt

* @返回

* @抛出异常声明

*/

公共静态声明解析器

secrettkey secrettkey=常规键();

返回Jwts.parser()。设置签名密钥(秘密密钥)。parseClaimsJws(jwt)。getBody();

}

}

验证实体信息用户用户:

/**

验证信息

*/公共类检查结果{

private int errCode

私有布尔成功;

私人索赔;

public int getErrCode() {

返回错误代码;

}

公共void setErrCode(int errCode) {

this.errCode=errCode

}

public boolean isSuccess() {

回报成功;

}

公共无效设置成功(布尔成功){

成功=成功;

}

公共索赔getClaims() {

退货要求;

}

公共无效集合声明(声明声明){

索赔=索赔;

}

}

拦截访问配置,跨域访问设置以及请求拦截过滤用户用户:

/**

拦截访问配置

*/@配置

公共类安全配置实现WebMvcConfigurer {

@Bean

公共SysInterceptor myInterceptor(){

返回新的SysInterceptor();

}

@覆盖

公共void addCorsmappings(CORS registry注册表){

registry.addMapping('/** ')。allowedOriginations( ' * ')。allowedMethods('GET ','HEAD ','POST ','PUT ','DELETE ','OPTIONS ')。allowCredentials(false).最大年龄(3600);

}

@覆盖

公共无效的附加接收器(截取注册注册表){

字符串[]模式=新字符串[]{ '/用户/登录 ','/* .html ' };

注册表。附加接收器(我的拦截器())。添加路径模式('/** ')。excludePathPatterns(模式);

}

}

拦截器统一权限校验用户用户:

/**

认证拦截器

*/公共类SysInterceptor实现HandlerInterceptor {

私有静态最终记录器记录器=记录器工厂。GetLogger(SysInterceptor。类);

@自动连线

私有系统用户服务系统用户服务

@覆盖

公共布尔预句柄(HttpServletRequest请求,HttpServletResponse响应,

对象处理程序){

if(处理程序方法的处理程序实例){

字符串AuthHeader=请求。getHeader( ' token ');

if(字符串油类。ISempty(AuthHeader)){

logger.info( '验证失败 ');

打印(响应,结果。错误(JwtUtils .JWT _错误_代码_空, '签名验证不存在,请重新登录 ');

返回错误的

}其他{

检查结果检查结果=JwtUtils.validateJWT(授权头);

if (checkResult.isSuccess()) {

/**

*权限验证

*/

字符串用户标识=检查结果。获取声明().getId();

句柄方法句柄方法=(处理方法)句柄;

注释角色注释=句柄方法GetAnnotation(需要角色。类);

if(roleAnnotation!=null){

字符串[]角色=句柄方法获取方法().getAnnotation(需要角色。class ).值();

逻辑逻辑=handlerMethod.getMethod().getAnnotation(需要角色。class ).逻辑();

列表字符串列表=SysUserService。GetRoleSignByUserId(整数。解析(用户标识);

int计数=0。

对于(整数1=0;irole.lengthi ){

如果(列表。包含(角色[一世)

计数;

如果(逻辑==逻辑。或)

继续;

}

}

}

如果(逻辑==逻辑。或)

if(count==0){

打印(响应,结果。错误( '无权限操作 ');

返回错误的

}

}其他{

如果(计数!角色长度)

打印(响应,结果。错误( '无权限操作 ');

返回错误的

}

}

}

返回真;

}其他{

开关(checkResult.getErrCode()) {

案例系统常数JWT错误代码失败:

logger.info( '签名验证不通过 ');

打印(响应,结果。错误(检查结果。获取错误代码(), '签名验证不通过,请重新登录 ');

休息;

案例系统常数JWT错误_代码_失效:

logger.info( '签名过期 ');

打印(响应,结果。错误(检查结果。获取错误代码(), '签名过期,请重新登录 ');

休息;

违约:

休息;

}

返回错误的

}

}

}其他{

返回真;

}

}

/**

*打印输出

* @param响应

* @param消息无效

*/

公共无效打印(HttpServletResponse响应,对象消息){

尝试{

回应。SetStatus(Https状态。好的。值();

response.setContentType(中介类型。应用_ JSON _ UTF8 _值);

响应。setHeader( '缓存-控制 ', '无缓存,必须-重新验证 ');

集合头( '访问控制-允许-来源 ', ' * ');

print writer writer=响应。GetWriter();

writer.write(消息).

作家。齐平();

作家。关闭();

}捕捉(异常e) {

e .printstackTrace();

}

}

}

配置角色注解,可以直接把安全框架Shiro的拷贝过来,如果有需要,菜单权限也可以配置上用户用户:

/**

权限注解

*/@目标({ElementType .类型,元素类型。方法)

@保留(保留政策.RUNTIME)

public @interface RequiresRoles {

/**

*方法需要一个字符串角色名或多个逗号分隔的角色名

*允许调用。

*/

字符串[]值();

/**

*在指定多个角色的情况下,权限检查的逻辑操作" .与"是默认值

* @自1.1.0起

*/

逻辑逻辑()默认逻辑。或;

}

模拟演示代码用户用户:

@RestController

@RequestMapping('/user ')

公共类用户控制器{

/**

*列表

* @返回

*/

@RequestMapping('/list ')

@ RequiresRoles角色角色(值='admin ')

公共结果列表(){

返回结果。确定( '十万亿个用户 ');

}

/**

*登录

* @返回

*/

@RequestMapping('/login ')

公共结果登录(){

/**

*模拟登录过程并返回代币

*/

字符串标记=JwtUtils.createJWT('101 ', ' 爪哇笔记 ',1000 * 60 * 60);

返回结果。ok(令牌);

}

}

前端请求模拟,发送请求之前在页眉中附带代币信息,更多代码见源码案例用户用户:

函数登录(){

美元.ajax({

网址: '/用户/登录 ',

键入: ' post ',

数据类型: ' json ',

成功:功能(数据){

if(data.code==0){

美元.cookie('token ',数据。味精);

}

},

错误:函数(XMLHttpRequest,textStatus,Error Hiddle){

}

});

}

函数用户(){

美元.ajax({

网址: '/用户/列表 ',

键入: ' post ',

数据类型: ' json ',

成功:功能(数据){

alert(data.msg)

},

发送前:功能(请求){

请求。SetRequestHeader( ' token ',美元.cookie( ' token ');

},

错误:函数(XMLHttpRequest,textStatus,Error Hiddle){

}

});

}

安全说明

JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用JWT的有效期建议设置的相对短一些。对于一些比较重要的权限,使用时应该再次对用户进行数据库认证。为了减少盗用JWT强烈建议使用HTTPS协议传输。

由于服务器不保存用户状态,因此无法在使用过程中注销某个令牌,或者更改代币的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

源码案例

https://gitee.com/52itstyle/safe-jwt

作者小柒

出处:https://blog.52itstyle.vip

转载地址https://www.cnblogs.com/smallSevens/p/12712744.html