准备spring-boot:2.1.4.release
spring-security-oauth3:2.3.3.release(如果要使用源码,不要随意改动这个版本号,因为2.4往上的写法不一样了)
mysql:5.7
效果展示这边只用了postman做测试,暂时未使用前端页面来对接,下个版本角色菜单权限分配的会有页面的展示
1、访问开放接口http://localhost:7000/open/hello
2、不带token访问受保护接口http://localhost:7000/admin/user/info
3、登录后获取token,带上token访问,成功返回了当前的登录用户信息
实现oauth3一共有四种模式,这边就不做讲解了,网上搜一搜,千篇一律
因为现在只考虑做单方应用的,所以使用的是密码模式。
后面会出一篇springcloud+oauth3的文章,网关鉴权
讲一下几个点吧
1、拦截器配置动态权限
新建一个 mysecurityfilter类,继承abstractsecurityinterceptor,并实现filter接口
初始化,自定义访问决策管理器
@postconstruct public void init(){ super.setauthenticationmanager(authenticationmanager); super.setaccessdecisionmanager(myaccessdecisionmanager); }
自定义 过滤器调用安全元数据源
@overridepublic securitymetadatasource obtainsecuritymetadatasource() { return this.mysecuritymetadatasource;}
先来看一下自定义过滤器调用安全元数据源的核心代码
以下代码是用来获取到当前请求进来所需要的权限(角色)
/** * 获得当前请求所需要的角色 * @param object * @return * @throws illegalargumentexception */ @override public collection<configattribute> getattributes(object object) throws illegalargumentexception { string requesturl = ((filterinvocation) object).getrequesturl(); if (is_change_security) { loadresourcedefine(); } if (requesturl.indexof("?") > -1) { requesturl = requesturl.substring(0, requesturl.indexof("?")); } urlpathmatcher matcher = new urlpathmatcher(); list<object> list = new arraylist<>(); //无需权限的,直接返回 list.add("/oauth/**"); list.add("/open/**"); if(matcher.pathsmatchesurl(list,requesturl)) return null; set<string> rolenames = new hashset(); for (resc resc: resources) { string rescurl = resc.getresc_url(); if (matcher.pathmatchesurl(rescurl, requesturl)) { if(resc.getparent_resc_id() != null && resc.getparent_resc_id().intvalue() == 1){ //默认权限的则只要登录了,无需权限匹配都可访问 rolenames = new hashset(); break; } map map = new hashmap(); map.put("resc_id", resc.getresc_id()); // 获取能访问该资源的所有权限(角色) list<rolerescdto> roles = rolerescmapper.findall(map); for (rolerescdto rr : roles) rolenames.add(rr.getrole_name()); } } set<configattribute> configattributes = new hashset(); for(string rolename:rolenames) configattributes.add(new securityconfig(rolename)); log.debug("【所需的权限(角色)】:" + configattributes); return configattributes; }
再来看一下自定义访问决策管理器核心代码,这段代码主要是判断当前登录用户(当前登录用户所拥有的角色会在最后一项写到)是否拥有该权限角色
@override public void decide(authentication authentication, object o, collection<configattribute> configattributes) throws accessdeniedexception, insufficientauthenticationexception { if(configattributes == null){ //属于白名单的,不需要权限 return; } iterator<configattribute> iterator = configattributes.iterator(); while (iterator.hasnext()){ configattribute configattribute = iterator.next(); string needpermission = configattribute.getattribute(); for (grantedauthority ga: authentication.getauthorities()) { if(needpermission.equals(ga.getauthority())){ //有权限,可访问 return; } } } throw new accessdeniedexception("没有权限访问"); }
2、自定义鉴权异常返回通用结果
为什么需要这个呢,如果不配置这个,对于前端,后端来说都很难去理解鉴权失败返回的内容,还不能统一解读,废话不多说,先看看不配置和配置了的返回情况
(1)未自定义前,没有携带token去访问受保护的api接口时,返回的结果是这样的
(2)我们规定一下,鉴权失败的接口返回接口之后,变成下面这种了,是不是更利于我们处理和提示用户
好了,来看一下是在哪里去配置的吧
我们资源服务器oautyresourceconfig,重写下下面这部分的代码,来自定义鉴权异常返回的结果
大伙可以参考下这个https://www.yisu.com/article/131668.htm
@override public void configure(resourceserversecurityconfigurer resources) throws exception { resources.authenticationentrypoint(authenticationentrypoint) //token失效或没携带token时 .accessdeniedhandler(requestaccessdeniedhandler); //权限不足时 }
3、获取当前登录用户
第一种:使用jwt携带用户信息,拿到token后再解析
暂不做解释
第二种:写一个securityuser实现userdetails接口(这个工程中使用的是这一种)
原来的只有userdetails接口只有username和password,这里我们加上我们系统中的user
protected user user; public securityuser(user user) { this.user = user; } public user getuser() { return user; }
在basecontroller,每个controller都会继承这个的,在里面写给getuser()的方法,只要用户带了token来访问,我们可以直接获取当前登录用户的信息了
protected user getuser() { try { securityuser userdetails = (securityuser) securitycontextholder.getcontext().getauthentication() .getprincipal(); user user = userdetails.getuser(); log.debug("【用户:】:" + user); return user; } catch (exception e) { } return null; }
那么用户登录成功后,如何去拿到用户的角色集合等呢,这里面就要实现userdetailsservice接口了
@servicepublic class tokenuserdetailsservice implements userdetailsservice{ @autowired private loginservice loginservice; @override public userdetails loaduserbyusername(string username) throws usernamenotfoundexception { user user = loginservice.loaduserbyusername(username); //这个我们拎出来处理 if(objects.isnull(user)) throw new usernamenotfoundexception("用户名不存在"); return new securityuser(user); }}
然后在我们的安全配置类中设置userdetailsservice为上面的我们自己写的就行
@override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.userdetailsservice(userdetailsservice).passwordencoder(passwordencoder()); }
最后我们只需要在loginservice里面实现我们的方法就好,根据我们的实际业务处理判断该用户是否存在等
@override public user loaduserbyusername(string username){ log.debug(username); map map = new hashmap(); map.put("username",username); map.put("is_deleted",-1); user user = usermapper.findbyusername(map); if(user != null){ map = new hashmap(); map.put("user_id",user.getuser_id()); //查询用户的角色 list<userroledto> userroles = userrolemapper.findall(map); user.setroles(listroles(userroles)); //权限集合 collection<? extends grantedauthority> authorities = merge(userroles); user.setauthorities(authorities); return user; } return null; }
数据库文件在这
以上就是springboot怎么整合springsecurityoauth2实现鉴权动态权限问题的详细内容。