成长记录贴之springboot+shiro(二) {完成一个完整的权限控制,详细步骤}

       近一个月比较忙,公司接了一个新项目,领导要求用shiro进行安全管理,而且全公司只有我一个java,从项目搭建到具体接口全是一个人再弄。不过刚好前段时间大概学习了一下shiro的使用,还算顺利。

       下面将项目中的shiro部分记录下来,为以后使用做一个备份。(因为是个人测试用的demo,好多地方再设计和实现的时候都是使用了最方便或者最简单的方法,希望不会误导各位看官)


*************************项目环境:springboot+jpa+hirbernate+mysql+shiro+maven*************************

前端很简陋,只是简单的用到了js,layui和vue,主要是为了方便展示数据


项目环境搭建和数据库点击这里查看,搭建完成后开始具体的权限管理工作


目录

项目结构

注册

1.首先在shiro的大管家ShiroConfig配置类中告诉大管家,注册的链接不需要拦截。

2.注册页面和实现

  2.1注册页面

  2.2注册实现

登录

1.验证码

 1.1先pom中添加jar包依赖(我用的Kaptcha,网上大部分都是用这个生成验证码)

 1.2在springboot启动类中注入生成验证码的bean()

1.3 写一个token类,集成shiro提供的UsernamePasswordToken

1.4写一个获取验证码的拦截器

1.5验证码异常类

1.6将验证码拦截器配置给shiro

1.7登录controller中增加验证码异常判断

1.8 登录页面放入验证码图片和验证码输入框

2.密码校验

3.权限校验

设置资源,角色

1.开启shiro的资源监控

2.设置用户角色、权限

2.1用户维护

2.2用户角色资源管理界面

2.3界面



项目结构

注册

1.首先在shiro的大管家ShiroConfig配置类中告诉大管家,注册的链接不需要拦截。

       

2.注册页面和实现


  2.1注册页面


    因为是测试demo我就只写一个简单的登录页面,只需要有一个form表单可以提交账号密码就行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>注册</title>
</head>
<body>
<form action="/register" method="post">
    用户名:<input type="text" name="username"/>
    <br/>
    密码:<input type="text" name="password"/>
    <br/>
    姓名:<input type="text" name="name"/>

    <input type="submit" value="注册"/>
</form>

</body>
</html>

  2.2注册实现

       这一步主要就是把密码用md5加盐加密,将加密后的密码和盐值存入数据库,然后返回登录页面

        (用户名重名校验啥的都没写,可以根据具体项目增加校验)

@RequestMapping("/register")
    public String register(UserInfo user) {
       String username=user.getUsername();
       String password1=user.getPassword();
        ByteSource salt = ByteSource.Util.bytes(username);
        String password = new SimpleHash("MD5", password1,username+salt,1024).toString();
        user.setSalt(salt.toString());
        user.setPassword(password);
        byte by=1;
        user.setState(by);
        dao.save(user);
       return "login";
    }

登录

1.验证码

 1.1先pom中添加jar包依赖(我用的Kaptcha,网上大部分都是用这个生成验证码)

<!-- 验证码jar-->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

 1.2在springboot启动类中注入生成验证码的bean()

 @Bean
    public ServletRegistrationBean kaptchaServlet() {

        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");

        registrationBean.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY,
                Constants.KAPTCHA_SESSION_KEY);
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT, "60");//高度
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "50");//字体大小
        registrationBean.addInitParameter(Constants.KAPTCHA_BORDER_THICKNESS, "1"); //边框
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "red"); //文字颜色

        //可以设置很多属性,具体看com.google.code.kaptcha.Constants
//      kaptcha.border  是否有边框  默认为true  我们可以自己设置yes,no
//      kaptcha.border.color   边框颜色   默认为Color.BLACK
//      kaptcha.border.thickness  边框粗细度  默认为1
//      kaptcha.producer.impl   验证码生成器  默认为DefaultKaptcha
//      kaptcha.textproducer.impl   验证码文本生成器  默认为DefaultTextCreator
//      kaptcha.textproducer.char.string   验证码文本字符内容范围  默认为abcde2345678gfynmnpwx
//      kaptcha.textproducer.char.length   验证码文本字符长度  默认为5
//      kaptcha.textproducer.font.names    验证码文本字体样式  默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
//      kaptcha.textproducer.font.size   验证码文本字符大小  默认为40
//      kaptcha.textproducer.font.color  验证码文本字符颜色  默认为Color.BLACK
//      kaptcha.textproducer.char.space  验证码文本字符间距  默认为2
//      kaptcha.noise.impl    验证码噪点生成对象  默认为DefaultNoise
//      kaptcha.noise.color   验证码噪点颜色   默认为Color.BLACK
//      kaptcha.obscurificator.impl   验证码样式引擎  默认为WaterRipple
//      kaptcha.word.impl   验证码文本字符渲染   默认为DefaultWordRenderer
//      kaptcha.background.impl   验证码背景生成器   默认为DefaultBackground
//      kaptcha.background.clear.from   验证码背景颜色渐进   默认为Color.LIGHT_GRAY
//      kaptcha.background.clear.to   验证码背景颜色渐进   默认为Color.WHITE
//      kaptcha.image.width   验证码图片宽度  默认为200
//      kaptcha.image.height  验证码图片高度  默认为50
        return registrationBean;

    }

 

1.3 写一个token类,集成shiro提供的UsernamePasswordToken

UsernamePasswordToken是shiro提供的用户令牌,我们扩展一下,用来接收用户输入的账号密码和验证码
package com.example.config;

import org.apache.shiro.authc.UsernamePasswordToken;

public class CaptchaUsernamePasswordToken extends UsernamePasswordToken {
    private static final long serivalVersionUID = 1L;

    //验证码字符串
    private String captcha;

    public CaptchaUsernamePasswordToken(String username, char[] password, boolean rememberMe, String host, String captcha) {
        super(username,password,rememberMe, host);
        this.captcha = captcha;
    }

    public static long getSerivalVersionUID() {
        return serivalVersionUID;
    }

    public String getCaptcha() {
        return captcha;
    }

    public void setCaptcha(String captcha) {
        this.captcha = captcha;
    }


}

1.4写一个获取验证码的拦截器

拦截到输入的验证码,与系统生成的验证码进行匹配,如果验证码错误则不再校验用户名和密码

package com.example.KaptchaFilter;

import com.example.Exception.IncorrectCaptchaException;
import com.example.config.CaptchaUsernamePasswordToken;
import com.google.code.kaptcha.Constants;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class KaptchaFilter extends FormAuthenticationFilter {

    public static final String DEFAULT_CAPTCHA_PARAM = "captcha";

    private String captchaParam = DEFAULT_CAPTCHA_PARAM;

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

        CaptchaUsernamePasswordToken token = createToken(request, response);
        String username = token.getUsername();

        try {
            doCaptchaValidate((HttpServletRequest) request, token);
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);

        } catch (AuthenticationException e) {
            return onLoginFailure(token,e,request,response);
        }
    }

    //验证码校验
    protected void doCaptchaValidate(HttpServletRequest request, CaptchaUsernamePasswordToken token) {

        // 从session中获取图形吗字符串
        String captcha = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);

        // 校验
        if (captcha == null || !captcha.equals(token.getCaptcha())) {
            throw new IncorrectCaptchaException();
        }
    }

    @Override
    protected CaptchaUsernamePasswordToken createToken(ServletRequest request, ServletResponse response) {

        String username = getUsername(request);
        String password = getPassword(request);
        String host = getHost(request);
        boolean rememberMe = isRememberMe(request);
        String captcha = getCaptcha(request);

        return new CaptchaUsernamePasswordToken(username,password.toCharArray(),rememberMe,host,captcha);
    }

    protected  String getCaptcha(ServletRequest request) {
        return WebUtils.getCleanParam(request, getCaptchaParam());
    }

    //保存异常对象到request
    @Override
    protected void setFailureAttribute(ServletRequest request, org.apache.shiro.authc.AuthenticationException ae) {
        request.setAttribute(getFailureKeyAttribute(), ae);
    }

    public String getCaptchaParam() {
        return captchaParam;
    }

    public void setCaptchaParam(String captchaParam) {
        this.captchaParam = captchaParam;
    }
}

验证码校验的时候抛出了一个异常,在homecontroller中会根据登录时shiro校验完成后的异常,判断登录状态和错误原因,这个异常就是用来判断验证码是否输入正确的。

因此我们需要写一个异常提供给shiro使用

1.5验证码异常类

package com.example.Exception;

import org.apache.shiro.authc.AuthenticationException;

public class IncorrectCaptchaException extends AuthenticationException {

    private static final long serivalVersionUID = 1L;

    public IncorrectCaptchaException() {
        super();
    }

    public IncorrectCaptchaException(String message, Throwable cause) {
        super(message, cause);
    }

    public IncorrectCaptchaException(String message) {
        super(message);
    }

    public IncorrectCaptchaException(Throwable cause) {
        super(cause);
    }
}

1.6将验证码拦截器配置给shiro

1.7登录controller中增加验证码异常判断

@RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
        Object ob=SecurityUtils.getSubject().getPrincipal();
        if(ob!=null){

            return "index";
        }
        System.out.println("HomeController.login()");
        // 登录失败从request中获取shiro处理的异常信息。
        // shiroLoginFailure:就是shiro异常类的全类名.
        Object exception =  request.getAttribute("shiroLoginFailure");
        System.out.println("exception=" + exception);
        System.out.println(IncorrectCredentialsException.class.getName());
        String msg = "";
        if (exception != null) {
            if (UnknownAccountException.class.isInstance(exception)) {
                msg = "UnknownAccountException -- > 账号不存在:";
            } else if (IncorrectCredentialsException.class.isInstance(exception)) {
                msg = "IncorrectCredentialsException -- > 密码不正确:";
            } else if (IncorrectCaptchaException.class.isInstance(exception)) {
                msg = "kaptchaValidateFailed -- > 验证码错误";
            } else {
                msg = "else >> "+exception;
            }
        }
        map.put("msg", msg);
        // 此方法不处理登录成功,由shiro进行处理
        return "login";
    }

1.8 登录页面放入验证码图片和验证码输入框

<div class="layui-inline" style="width: 85%">
                <label class="layui-form-label">验证码</label>
                <div  class="layui-inline">
                    <input type="text" id="captcha" name="captcha"   lay-verify="required" placeholder="请输入验证码" autocomplete="off" class="layui-input"/>
                </div>
                <div  class="layui-inline"><img src="kaptcha.jpg" id="kaptchaImage" /></div>
            </div>

2.密码校验

这里需要做的只是将根据用户输入的用户名查询出来的userinfo扔给shiro专门用来校验密码的方法doGetAuthenticationInfo,让shiro自己去验证就行,关于如何修改shiro的验证方法,比如修改解密算法什么的,在我上一贴中有介绍,这里就不多说了

点击这里查看上一贴

 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        String password = new String((char[])token.getCredentials()); //得到密码

        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userInfoService.findByUsername(username);
        System.out.println("----->>userInfo="+userInfo);
        System.out.print(userInfo.getPassword());
        if(userInfo == null){
            return null;
        }
        /*
         * 获取权限信息:这里没有进行实现,
         * 请自行根据UserInfo,Role,Permission进行实现;
         * 获取之后可以在前端for循环显示所有链接;
         */
        //userInfo.setPermissions(userService.findPermissions(user));
        System.out.println(userInfo.getCredentialsSalt());
        System.out.println(ByteSource.Util.bytes(userInfo.getCredentialsSalt()));
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //用户名
                userInfo.getPassword(), //密码
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                getName()  //realm name
        );
        return authenticationInfo;
    }

3.权限校验

       这里个人理解是shiro提供了一个用来放用户授权信息的对象SimpleAuthorizationInfo和一个用来放身份信息的集合PrincipalCollection。我们从PrincipalCollection中获取当前登录的用户对象,因为用户对象、角色对象、资源对象三者存在关联关系,在设计数据库时已经设计好了关联关系,并且三个对象已经用jpa进行了关了,可以直接从其中一个对象中获取关联的其他对象。而shiro的权限管理不需要我们操作。

        所以这里的权限管理只需要从PrincipalCollection中获取用户对象,再用户对象中获取到角色对象,再从角色对象中获取资源对象,最后将这些对象放入到SimpleAuthorizationInfo中,让shiro自己去给我们判断用户是否有访问权限就可以了

 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();


        UserInfo userInfo  = (UserInfo) principals.getPrimaryPrincipal();
       // BeanUtils.copyProperties(oo,userInfo);


        for(SysRole role:userInfo.getRoleList()){
            authorizationInfo.addRole(role.getRole());
            for(SysPermission p:role.getPermissions()){
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }

这里有一点需要注意,在

UserInfo userInfo  = (UserInfo) principals.getPrimaryPrincipal();

的时候可能会,报com.example.entity.UserInfo cannot be cast to com.example.entity.UserInfo,

在网上找到的办法是将springboot的热部署关掉就ok了,但是原理不知道,希望有大佬解答

设置资源,角色

1.开启shiro的资源监控

我们将需要保护的资源或者链接纳入shiro控制的方位内,具体做法就是在controller方法前加上@RequiresPermissions("view")注解,括号里为资源名称,与数据库资源表中的字段对应。资源名称支持通配符,可以写成

@RequiresPermissions("userInfo:view") 表示查看用户
@RequiresPermissions("userInfo:view,add")表示查看和增加用户
@RequiresPermissions("userInfo:view:123")表示只能查看id为123的用户 这一点没有测试,有时间了再测试一下具体用法
@RequestMapping("/userManager")
    @RequiresPermissions("userInfo:manager")//权限管理;
    public String userManager(){
        return "userInfoManager";
    }

为了能让系统识别@RequiresPermissions注解,需要在shiroconfig配置中开启改注解

 @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
 @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

 @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

2.设置用户角色、权限

2.1用户维护

用户查看界面

增加一个简单的用户查询界面,用来获取全部用户,并可以查看和分配用户角色

注:之后的页面代码都是套用的这个页面,就不贴出来了

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>UserInfo</title>
    <script type="text/javascript" th:src="@{../static/source/js/layui/layui.js}"></script>
    <script type="text/javascript" th:src="@{../static/source/js/layui/layui.all.js}"></script>
    <script type="text/javascript" th:src="@{../static/vue.min.js}"></script>
    <script type="text/javascript" th:src="@{../static/jquery-3.3.1.min.js}"></script>
    <link rel="stylesheet" th:href="@{../static/source/js/layui/css/layui.css}"/>
</head>
<body>
    <h3>用户查询界面</h3>
    <div id="userinfo">
        <table class="layui-table">
            <thead>
            <tr>
                <th>用户名1111</th>
                <th>姓名</th>
                <th>id</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="site in tablePerson">
                <th>{{site.name}}</th>
                <th>{{site.username}}</th>
                <th>{{site.uid}}</th>
                <th><span @click="changeRole(site.uid)" >查看</span>
                    <span @click="allotRole(site.uid)" >分配角色</span>
                </th>
            </tr>

            </tbody>
        </table>

    </div>
</body>
<script>
    var userinfo = new Vue({
        el: '#userinfo',
        data: {
            tablePerson: []
        },
        created: function () {
            //为了在内部函数能使用外部函数的this对象,要给它赋值了一个名叫self的变量。
            var self = this;
            var codeurl = "/userInfo/queryUser";
            $.ajax({
                type: 'get',
                url: codeurl,
                async: false,
                contentType: 'application/json;charset=UTF-8',
            }).then(function (res) {
                console.log(res.data);
                console.log("###res"+JSON.stringify(res));

                //把从json获取的数据赋值给数组

                self.tablePerson = res;
                console.log(self);
            }).fail(function () {
                console.log('失败');
            })
        },

    });

    function changeRole(id) {
        var url="userManager";
        layer.open({
            type: 2,
            skin: 'layui-layer-lan',
            title: '角色管理',
            fix: false,
            shadeClose: false,
            maxmin: true,
            id:'selectUser',
            move: false,
            closeBtn:2,
            //以下代码为打开窗口添加按钮
            /* btn: ['确定', '取消'],
            btnAlign: 'c',
            yes: function(index, layero){
                /* //layer.closeAll();//关闭所有弹出层
                //var parentWin = layero.find('iframe')[0];
                var parentWin  = layer.getChildFrame('body', index);
                alert(parentWin);
                parentWin.contentWindow.doOk();
                //layer.close(index);//这块是点击确定关闭这个弹出层
            }, */
            area: ['750px', '450px'],
            content: url,
            success: function (layero, index) {
                // 获取子页面的iframe
                var iframe = window['layui-layer-iframe' + index];
                // 向子页面的全局函数child传参
                iframe.child(id);
            },
            end: function () {
                document.getElementById('groupName').value = "";
            }

        });

        //     console.log(id);
        // var codeurl = "/userInfo/userManager";
        // var data={"userid":id};
        // $.ajax({
        //     type: 'get',
        //     url: codeurl,
        //     data:data,
        //     async: false,
        //     contentType: 'application/json;charset=UTF-8',
        // }).then(function (res) {
        //     console.log(JSON.stringify(res.data));
        //     location.reload();
        //
        // }).fail(function () {
        //     console.log('失败');
        // })
    }

    function allotRole(uid){
        var url="allotRole";
        layer.open({
            type: 2,
            skin: 'layui-layer-lan',
            title: '角色分配',
            fix: false,
            shadeClose: false,
            maxmin: true,
            id:'selectUser',
            move: false,
            closeBtn:2,
            //以下代码为打开窗口添加按钮
            /* btn: ['确定', '取消'],
            btnAlign: 'c',
            yes: function(index, layero){
                /* //layer.closeAll();//关闭所有弹出层
                //var parentWin = layero.find('iframe')[0];
                var parentWin  = layer.getChildFrame('body', index);
                alert(parentWin);
                parentWin.contentWindow.doOk();
                //layer.close(index);//这块是点击确定关闭这个弹出层
            }, */
            area: ['750px', '450px'],
            content: url,
            success: function (layero, index) {
                // 获取子页面的iframe
                var iframe = window['layui-layer-iframe' + index];
                // 向子页面的全局函数child传参
                iframe.child(uid);
            },
            end: function () {
                document.getElementById('groupName').value = "";
            }

        });

    }
</script>
</html>

 

这里有一点需要注意的,程序知行到这里的时候,一直报java.lang.IllegalStateException: Cannot call sendError() after the response has been committed这个错误,后来咨询过大佬之后,大概明白了。

因为我的user实体中关联着role实体,而role又关联着user实体,所以在序列化实体的时候就会进入到死循环的状态,

解决办法就是在实体的get,set方法前加上@JsonBackReference注解。告诉程序不要级联查询,不过这样做的问题就是,在序列化user实体的时候,不会返回role的值。

用户管理controller

package com.example.controller;

import com.example.dao.UserInfoDao;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/userInfo")
public class UserInfoController {
@Autowired
    UserInfoDao userInfoDao;
    /**
     * 用户查询.
     * @return
     */
    @RequestMapping("/userList")
    @RequiresPermissions("userInfo:view")//权限管理;
    public String userInfo(){
        return "userInfo";
    }

    /**
     * 用户添加;
     * @return
     */
    @RequestMapping("/userAdd")
    @RequiresPermissions("userInfo:add")//权限管理;
    public String userInfoAdd(){
        return "userInfoAdd";
    }

    /**
     * 用户删除;
     * @return
     */
    @RequestMapping("/userDel")
    @RequiresPermissions("userInfo:del")//权限管理;
    public String userDel(){
        return "userInfoDel";
    }
    @RequestMapping("/userManager")
    @RequiresPermissions("userInfo:manager")//权限管理;
    public String userManager(){
        return "userInfoManager";
    }

    /**
     * 获取全部用户信息
     * @return
     */
    @RequestMapping(value="queryUser", method= RequestMethod.GET)
   // @RequiresPermissions("userInfo:queryUser")//权限管理;
    @ResponseBody
    public List queryUser(){
        List list= userInfoDao.findAllUser();

        return list;
    }
    @RequestMapping(value="querypermission", method= RequestMethod.GET)
    // @RequiresPermissions("userInfo:queryUser")//权限管理;
    public String queryPermission(){


        return "permissionman";
    }
    @RequestMapping(value="allotRole", method= RequestMethod.GET)
    // @RequiresPermissions("userInfo:queryUser")//权限管理;
    public String allotRole(){


        return "allotRole";
    }


}

2.2用户角色资源管理界面

添加用户、删除用户、取消授权等操作在demo里没有写,步骤就是增加用户与角色对照表实体,角色与资源对照表实体,在取消授权或增加授权的时候,操作对照表实体,在数据库中增加对应数据就可以了。

实现的时候将增加资源和角色的权限控制起来,设置只有管理员或上级角色才可以增加删除

下面是权限控制的例子,可以通过类似的方法控制增加和删除的权限

@RequestMapping("/userManager")
    @RequiresPermissions("userInfo:manager")//只有用户具有userInfo:manager 权限的时候才可以访问
                                            //可以修改为role:xxx 控制用户是否能操作角色,
                                            //或xxx:xxx 控制对应的资源
    public String userManager(){
        return "userInfoManager";
    }

2.3界面

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值