回弹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
发表评论