从零开始,手打一个权限管理系统(第五章权限控制)
前言
这章主要通过SpringSecurity来实现对权限的控制,权限粒度是到每个方法。一、token验证
第四章登录我们获取到了token,每次请求的时候都必须验证这个token是否合法、是否过期,所以我们需要一个拦截器来拦截每一次的请求;这里我们可以通过继承OncePerRequestFilter来实现我们对token的验证;当然并不是所有请求都需要拦截,所以还需要一个白名单,来配置不需要被拦截的请求。Slf4jConfigurationConfigurationProperties(prefixsecurity。white)publicclassPermitUrlProperties{GetterSetterprivateListStringurlsnewArrayList();}
yml配置:security:white:urls:loginlogout
JwtAuthenticationTokenFiltertoken拦截验证Slf4jpublicclassJwtAuthenticationTokenFilterextendsOncePerRequestFilter{AutowiredprivateJWTUtiljwtUtil;AutowiredprivatePermitUrlPropertiespermitUrlProperties;OverrideprotectedvoidinitFilterBean()throwsServletException{System。out。println(JwtAuthenticationTokenFilter初始化。。。);}OverrideprotectedvoiddoFilterInternal(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,FilterChainfilterChain)throwsServletException,IOException{StringrequestUrlhttpServletRequest。getRequestURI();log。info(请求url:{},requestUrl);白名单url放过if(filterWhiteUrl(requestUrl)){filterChain。doFilter(httpServletRequest,httpServletResponse);return;}StringauthTokenhttpServletRequest。getHeader(SecurityConstants。AUTHORIZATION);if(StrUtil。isBlank(authToken)){ResultStringresultResult。fail();result。setMsg(未登录);ResponseUtil。response(httpServletResponse,result);return;}booleancheckTokenjwtUtil。checkToken(authToken);if(checkToken){ResultStringresultResult。fail();result。setMsg(会话已过期,请重新登录);httpServletResponse。setStatus(HttpStatus。HTTPUNAUTHORIZED);ResponseUtil。response(httpServletResponse,result);return;}if(SecurityContextHolder。getContext()。getAuthentication()null){Context中的认证为空,进行token验证ClaimsclaimsjwtUtil。getClaimsFromToken(authToken);从jwt中恢复用户信息和权限Stringidclaims。get(JWTUtil。ID,String。class);StringorgIdclaims。get(JWTUtil。ORGID,String。class);Stringusernameclaims。get(JWTUtil。USERNAME,String。class);Stringauthoritiesclaims。get(JWTUtil。AUTHORITIES,String。class);ListStringlistJSON。parseObject(authorities,newTypeReferenceListString(){});JwtUserjwtUsernewJwtUser(id,orgId,username,,AuthorityUtils。createAuthorityList(list。toArray(newString〔0〕)));如username不为空,并且能够在数据库中查到JwtAuthenticationTokenjwtAuthenticationTokennewJwtAuthenticationToken(jwtUser。getAuthorities(),jwtUser,null);将authentication放入SecurityContextHolder中SecurityContextHolder。getContext()。setAuthentication(jwtAuthenticationToken);}filterChain。doFilter(httpServletRequest,httpServletResponse);}过滤表名单的urlparamurlreturnprivatebooleanfilterWhiteUrl(Stringurl){ListStringwhiteListpermitUrlProperties。getUrls();if(CollectionUtil。isNotEmpty(whiteList)){PathMatchermatchernewAntPathMatcher();for(StringreleaseUrl:whiteList){booleanmatchmatcher。match(releaseUrl,url);if(match){returntrue;}}}returnfalse;}}
更新下SpringSecurityConfigurer,将JwtAuthenticationTokenFilter加入配置中,部分代码如下:http。addFilterAfter(jwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter。class);BeanJwtAuthenticationTokenFilterjwtAuthenticationTokenFilter(){returnnewJwtAuthenticationTokenFilter();}
经过一系列的编译调试后,启动项目验证:
1、获取token
2、不带token访问主页
3、带token访问主页
4、token错误和过期访问主页
二、权限验证1、开启全局安全配置
在SpringSecurityConfigurer上加上EnableGlobalMethodSecurity(prePostEnabledtrue)就可以了;他会解锁PreAuthorize和PostAuthorize两个注解,PreAuthorize会在方法执行前进行验证,PostAuthorize会在方法执行后进行验证。2、标记需要校验的方法
我们在IndexController上面加上权限校验,即PreAuthorize(hasAuthority(sys:index))
3、自定义未授权处理器
实现AccessDeniedHandler的handle接口即可
JwtAccessDeniedHandler未授权访问处理publicclassJwtAccessDeniedHandlerimplementsAccessDeniedHandler{Overridepublicvoidhandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,AccessDeniedExceptione)throwsIOException,ServletException{ResultStringresultResult。fail(e。getMessage());httpServletResponse。setStatus(HttpStatus。HTTPFORBIDDEN);ResponseUtil。response(httpServletResponse,result);}}
把这个加到SpringSecurityConfigurer里面,新增代码如下:。exceptionHandling((execption)execption未授权异常处理。accessDeniedHandler(newJwtAccessDeniedHandler()));
测试未授权
测试已授权
在JwtUserDetailsServiceImpl的权限列表中加入我们刚刚加的权限标记sys:index
重新登录,获取新的token,并请求主页,发现能够正常访问
3、通过数据库配置权限
前面都是写死的权限,实际项目都是从数据库中查询的,这个项目我们采用RBAC基于角色的访问控制,将所有权限都赋给角色,将角色赋给具体的用户。3。1、表设计
用户表sysuser,用来存放用户名、密码等基础信息CREATETABLEsysuser(idvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb4binNOTNULLCOMMENT主键ID,usernamevarchar(64)CHARACTERSETutf8mb4COLLATEutf8mb4binNOTNULLCOMMENT用户名,passwordvarchar(255)CHARACTERSETutf8mb4COLLATEutf8mb4binNOTNULL,phonevarchar(20)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULTNULLCOMMENT电话,avatarvarchar(255)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULTNULLCOMMENT头像,orgidvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULTNULLCOMMENT机构ID,createtimetimestampNULLDEFAULTCURRENTTIMESTAMPCOMMENT创建时间,updatetimetimestampNULLDEFAULTCURRENTTIMESTAMPONUPDATECURRENTTIMESTAMPCOMMENT修改时间,statuschar(1)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULT1COMMENT1正常,0锁定,delflagchar(1)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULT1COMMENT逻辑删除标记(1:显示;0:删除),PRIMARYKEY(id)USINGBTREE,UNIQUEINDEXukusername(username)USINGBTREE)ENGINEInnoDBCHARACTERSETutf8mb4COLLATEutf8mb4binCOMMENT用户表ROWFORMATDynamic;
组织机构表sysorgCREATETABLEsysorg(idvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNOTNULL,parentidvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULTNULL,sortintNULLDEFAULT1COMMENT排序,typechar(2)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNOTNULLCOMMENT机构类型,codevarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNOTNULLCOMMENT机构编码,namevarchar(50)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULTNULLCOMMENT机构名称,phonevarchar(20)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT电话,emailvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT邮箱,addressvarchar(500)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT地址,remarksvarchar(255)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT备注,delflagchar(1)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULT1COMMENT逻辑删除标记(1:显示;0:删除),statuschar(2)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT1:正常,0:锁定,createtimetimestampNULLDEFAULTCURRENTTIMESTAMPCOMMENT创建时间,updatetimetimestampNULLDEFAULTNULLONUPDATECURRENTTIMESTAMPCOMMENT修改时间,PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBCHARACTERSETutf8mb4COLLATEutf8mb40900aiciCOMMENT机构管理ROWFORMATDynamic;
菜单表sysmenu,存放对应的菜单和权限标识CREATETABLEsysmenu(idvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULLCOMMENT菜单ID,titlevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULLCOMMENT菜单名称,permissionvarchar(64)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULTNULLCOMMENT权限标识,parentidvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULTNULLCOMMENT父菜单ID,sortintNOTNULLDEFAULT0COMMENT排序值,typechar(1)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULTNULLCOMMENT菜单类型(0菜单1按钮),createtimetimestampNOTNULLDEFAULTCURRENTTIMESTAMPCOMMENT创建时间,updatetimetimestampNOTNULLDEFAULTCURRENTTIMESTAMPONUPDATECURRENTTIMESTAMPCOMMENT更新时间,delflagchar(1)CHARACTERSETutf8COLLATEutf8generalciNULLDEFAULT1COMMENT逻辑删除标记(1:显示;0:删除),PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBCHARACTERSETutf8COLLATEutf8generalciCOMMENT菜单权限表ROWFORMATDynamic;
角色表sysroleCREATETABLEsysrole(idvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb4binNOTNULLCOMMENT主键,rolenamevarchar(64)CHARACTERSETutf8mb4COLLATEutf8mb4binNOTNULLCOMMENT角色名,rolecodevarchar(64)CHARACTERSETutf8mb4COLLATEutf8mb4binNOTNULLCOMMENT角色编码,roledescvarchar(255)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULTNULLCOMMENT角色描述,createtimetimestampNOTNULLDEFAULTCURRENTTIMESTAMPCOMMENT创建时间,updatetimetimestampNULLDEFAULTNULLONUPDATECURRENTTIMESTAMPCOMMENT修改时间,delflagchar(1)CHARACTERSETutf8mb4COLLATEutf8mb4binNULLDEFAULT1COMMENT逻辑删除标记(1:显示;0:删除),PRIMARYKEY(id)USINGBTREE,UNIQUEINDEXroleidrolecode(rolecode)USINGBTREE)ENGINEInnoDBCHARACTERSETutf8mb4COLLATEutf8mb4binCOMMENT系统角色表ROWFORMATDynamic;
角色菜单关系表sysrolemenu,一个角色拥有哪些菜单的权限CREATETABLEsysrolemenu(roleidvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULLCOMMENT角色ID,menuidvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULLCOMMENT菜单ID,createtimetimestampNULLDEFAULTCURRENTTIMESTAMPCOMMENT创建时间,PRIMARYKEY(roleid,menuid)USINGBTREE)ENGINEInnoDBCHARACTERSETutf8COLLATEutf8generalciCOMMENT角色菜单表ROWFORMATDynamic;
用户角色表sysuserrole,一个用户拥有哪些角色CREATETABLEsysuserrole(useridvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULLCOMMENT用户ID,roleidvarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULLCOMMENT角色ID,createtimetimestampNULLDEFAULTCURRENTTIMESTAMPCOMMENT创建时间,PRIMARYKEY(userid,roleid)USINGBTREE)ENGINEInnoDBCHARACTERSETutf8COLLATEutf8generalciCOMMENT用户角色表ROWFORMATDynamic;
用户和角色是一对多的关系,角色和菜单也是一对多的关系
3。2、创建实体和实现CRUD
写这些类其实是一个重复的工作,把这个项目写完了,一定要做一个代码生成器,一个一个地敲太费时费力了!!
4、测试验证4。1、初始化数据
之前我们开启了权限验证,现在初始化数据的时候先关一下;只需要注释掉SpringSecurityConfigurer上的EnableGlobalMethodSecurity(prePostEnabledtrue)这个注即可。
接口文档:从零开始手打一个权限管理系统
初始化菜单数据
取消注释,发送登录请求,可以发现权限信息已经全部写进去了,大家会发现新生成的token会比之前大很多,因为写入了权限信息,具体代码可看JWTUtil的createToken方法
测试访问没有权限的主页
测试有权限的用户新增
看看能不能登录
到这里,这个系统的基本功能大部分都完成了,接下来我将继续完善和优化细节!!!
当前版本:1。0。4
〔代码仓库〕(https:gitee。comailotstudy)三、体验地址(http:test。ailot。vip)
后台数据库只给了部分权限,报错属于正常!想学的老铁给点点关注吧!!!后期会开源前后端所有代码!!!
我是阿咕噜,一个从互联网慢慢上岸的程序员,如果喜欢我的文章,记得帮忙点个赞哟,谢谢!