springboot整合shiro实现权限控制

springboot整合shiro实现权限控制ApacheShiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。上个月写了一个在线教育的项目用到了shiro权限控制,这几天又复盘了一下,对其进行了深入探究,来总结一下。下面所总结的有关shiro的代码已经传到我的github上,可以访问下面的……

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

博主简介:原互联网大厂tencent员工,网安巨头Venustech员工,阿里云开发社区专家博主,微信公众号java基础笔记优质创作者,csdn优质创作博主,创业者,知识共享者,欢迎关注,点赞,收藏。

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。上个月写了一个在线教育的项目用到了shiro权限控制,这几天又复盘了一下,对其进行了深入探究,来总结一下。

下面所总结的有关shiro的代码已经传到我的github上,可以访问下面的链接:

GitHub – xujiankang6/shirotest

一、实现功能

1、完成了记住我功能

2、完成了密码加密功能

3、完成了根据shiro权限访问不同内容

4、完成了使用shiro的session进行保存

二、实现代码

1、引入shiro相关的依赖

我是前端使用了thymeleaf,所以需要引入下面shiro相关的依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
</dependency>
<dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
</dependency>

Jetbrains全家桶1年46,售后保障稳定

2、自定义密码验证器

(1)这个类实现了shiro的SimpleCredentialsMatcher接口来重新密码验证方法

(2)同时写了加密的方法encrypt(String data),拿到前台传过来的密码后,使用该方法加密后与数据库的拿到的密码进行比对,返回ture或者fasle。

(3)当然,前台注册时,保存数据库的密码也需要用同样的形式把密码加密后再保存。

//验证密码 查找到了1该用户 自定义密码验证器
public class MyMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String pwd = encrypt(String.valueOf(usernamePasswordToken.getPassword()));
        String mysqlpwd = (String) info.getCredentials();
        return this.equals(pwd, mysqlpwd);
    }


    //将传进来的密码进行加密的方法
    private String encrypt(String data){
        String sha384Hex=new Sha384Hash(data).toBase64();
        return sha384Hex;
    }

}

3、自定义realm

当密码验证通过后,就到了我们的自定义realm,在我们自定义realm中实现了AuthorizingRealm接口,将其方法进行重写,将各种权限对用户进行授权,同时对用户身份进行验证,代码如下,每一行代码具体含义十分详细了。

//登录及权限验证
public class MyRealm extends AuthorizingRealm {

    @Autowired
    UserService us;

  //角色权限和对应权限添加
    //Authorization授权,将数据库中的角色和权限授权给输入的用户名
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录的用户名
        String phone = (String) principalCollection.getPrimaryPrincipal();
        //到数据库里查询要授权的内容
        User user = us.querybyname(phone);
        //记录用户的所有角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();//权限信息
        for(Role r:user.getRoles()){
            //将所有的角色信息添加进来。
            simpleAuthorizationInfo.addRole(r.getRname());
            for(Permission p:r.getPermissions()){
                //将此次遍历到的角色的所有权限拿到,添加·进来
                simpleAuthorizationInfo.addStringPermission(p.getPname());
            }
        }
        return simpleAuthorizationInfo;
    }

    //用户身份验证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //从token获取用户名,从主体传过来的认证信息中获取
        //加这一步的目的是在post请求时会先进入认证然后再到请求。
        if(authenticationToken.getPrincipal()==null){
            return null;
        }
        //获取用户的登录信息,用户名
        String phone=authenticationToken.getPrincipal().toString();

        //根据service调用用户名,查找用户的全部信息
        //通过用户名到数据库获取凭证
        User user=us.querybyname(phone);
        if(user==null){
            //这里返回会报出对应异常
            return  null;
        }else{
            //这里验证authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(phone,user.getUpwd().toString(),getName());
            return simpleAuthenticationInfo;
        }

    }

4、自定义记住我过滤器

该过滤器是为了实现记住我后,用户再次登陆不需要进行权限验证,就能到达首页,后面会介绍当不使用该过滤器的后果。

(1)该过滤器实现当用户通过isRemembered()登陆,没有通过isAuthenticated()登陆时拿到user的session信息,保证后面到达首页时候能拥有各种跟通过isAuthenticated()登陆时的session信息。

(2)过滤器完成了登陆条件的过滤,要么通过权限认证登陆成功,要么通过记住我登陆成功。

(3)在shiroconfig类中会进行shiro访问权限配置。

public class MyRememberFilter extends FormAuthenticationFilter {

    protected boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response, Object mappedValue){
        Subject subject=getSubject(request,response);
        if(!subject.isAuthenticated() && subject.isRemembered()){
            if(subject.getSession().getAttribute("user")==null &&subject.getPrincipal()!=null){
                subject.getSession().setAttribute("user",subject.getPrincipal());
            }

        }

        return subject.isAuthenticated() || subject.isRemembered();
    }
}

5、实现shiroConfig配置类

(1)配置密码验证器

 @Bean("credentialsMatcher")
    public CredentialsMatcher credentialsMatcher() {
        return new MyMatcher();
    }

(2)配置权限验证器

@Bean("myRealm")
    public MyRealm myRealm(@Qualifier("credentialsMatcher") CredentialsMatcher credentialsMatcher) {
        MyRealm myRealm = new MyRealm();
        //给权限验证器配置上自定义的密码验证器
        myRealm.setCredentialsMatcher(credentialsMatcher);
        return myRealm;
    }

(3)配置缓存验证器

    @Bean
    public CacheManager cacheManager(){
        return new MemoryConstrainedCacheManager();
    }

(4)配置记住我Cookie对象参数,rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。

    public SimpleCookie rememberMeCookie() {
//        这个参数是cookie的名称,对应前端的checkbox的name=rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//        cookie生效时间为10秒
        simpleCookie.setMaxAge(10);
        return simpleCookie;
    }

(5)配置Cookie管理对象,rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中

    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return  cookieRememberMeManager;
    }

(6)注入自定义记住我过滤器

    @Bean
    public MyRememberFilter MyRememberFilter(){
        return new MyRememberFilter();
    }

(7)配置securityManager安全管理器,主要起到一个桥梁作用。

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //注入自定义myRealm
        defaultWebSecurityManager.setRealm(myRealm);
        //注入自定义cacheManager
        defaultWebSecurityManager.setCacheManager(cacheManager());
        //注入记住我管理器
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        //注入自定义sessionManager
        defaultWebSecurityManager.setSessionManager(sessionManager());
        return defaultWebSecurityManager;
    }

(8)进行全局配置,Filter工厂。设置对应的过滤条件和跳转条件,有自定义的过滤器,有shiro认证成功后,失败后,退出后等跳转的页面,有静态页面等内容的权限范围。

    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        //shiro对象
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");

        Map<String, Filter> filterMap=new LinkedHashMap<String,Filter>();
       filterMap.put("MyRememberFilter",MyRememberFilter());

        //MAP
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>();
        /*
        认证顺序是从上往下执行。
         */
        linkedHashMap.put("/logout", "logout");//在这儿配置登出地址,不需要专门去写控制器。
        linkedHashMap.put("/static/**", "anon");
        //开启注册页面不需要权限
        linkedHashMap.put("/register", "anon");
        linkedHashMap.put("/saveregister", "anon");
        //验证phone唯一
        linkedHashMap.put("/solephone", "anon");
        //获取验证码
        linkedHashMap.put("/getcode", "anon");
        //验证码判断
        linkedHashMap.put("/comparecode", "anon");
        linkedHashMap.put("/websocket", "anon");//必须开启。
        linkedHashMap.put("/css/**", "anon");//不需要验证
        linkedHashMap.put("/js/**", "anon");//不需要验证
        //配置错误页面
        linkedHashMap.put("error", "anon");//不需要验证
        linkedHashMap.put("/img/**", "anon");//不需要验证
        linkedHashMap.put("/layui/**", "anon");//不需要验证
        linkedHashMap.put("/video/**", "anon");//不需要验证
        linkedHashMap.put("/bower_components/**", "anon");//不需要验证
        linkedHashMap.put("/plugins/**", "anon");//不需要验证
        linkedHashMap.put("/dist/**", "anon");//不需要验证
        linkedHashMap.put("/**", "user");//需要进行权限验证
        bean.setFilterChainDefinitionMap(linkedHashMap);
        return bean;
    }

(9)配置shiro的生命周期

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

(10)配置shiro注解是否生效

启动Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证

配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能

@Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        sourceAdvisor.setSecurityManager(securityManager);
        return sourceAdvisor;
    }

(11)配置前台的shiro标签,使其能够使用

前台是使用shiro结合thymeleaf实现细粒度权限控制的。

    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

6、用户登陆controller实现

(1)当用户第一次到达登陆页面时,通过get方式。

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(@Param("phone") String phone, @Param("upwd") String upwd) {

        return "login";

    }

(2)用户提交登陆信息时,通过post方法。

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ModelAndView login2(@Param("phone") String phone, @Param("upwd") String upwd) {
        ModelAndView m = new ModelAndView();
        //添加用户认证信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken uToken = new UsernamePasswordToken(phone, upwd);
        //实现记住我
        uToken.setRememberMe(true);
        try {
            //进行验证,报错返回首页,不报错到达成功页面。
            subject.login(uToken);

        } catch (UnknownAccountException e) {
            m.addObject("result", "用户不存在");
            m.setViewName("login");
            return m;
        } catch (IncorrectCredentialsException e) {
            m.addObject("result", "密码错误");
            m.setViewName("login");
            return m;
        }
        //将权限信息保存到session中
        User user = sd.querybyname(phone);

        List<Permission> permissions = new ArrayList<Permission>();
        for (Role role : user.getRoles()) {
            for (Permission permission : role.getPermissions()) {
                permissions.add(permission);
            }
        }
        Map<String, ArrayList<Permission>> map = new HashMap<String, ArrayList<Permission>>();
        for (Permission p : permissions) {
            String name = p.getPermission_group_name();
            if (!map.containsKey(name)) {
                ArrayList<Permission> mList = new ArrayList<Permission>();
                mList.add(p);
                map.put(name, mList);
            } else {
                ArrayList<Permission> pList = map.get(name);
                pList.add(p);
                map.put(name, pList);
            }
        }
//       保存到shiro的session中一些信息
        Session session = subject.getSession();
//        保存userinfo的基本信息
        int uid = user.getUid();
        UserInfo userInfo = sd.queryuserinfo(uid);
        //保存user信息
        session.setAttribute("user", user);
//shiro权限验证成功后跳转的界面
        m.setViewName("index");
        return m;
    }

7、用户前端登录页面

(1)部分代码

<div class="login-box-body">
        <p class="login-box-msg">登录更精彩!!!</p>
        <form action="login" method="post">
            <div class="form-group has-feedback">
                <input type="phone" class="form-control" placeholder="请输入手机号" name="phone">
                <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
            </div>
            <div class="form-group has-feedback">
                <input type="password" class="form-control" placeholder="请输入密码" name="upwd">
                <span class="glyphicon glyphicon-lock form-control-feedback"></span>
            </div>
            <div class="row">
                <div class="col-xs-8">
                    <div class="checkbox icheck">
                        <label>
                            <input type="checkbox" name="rememberMe"> 记住账户
                        </label>
                    </div>
                </div>
                <!-- /.col -->
                <div class="col-xs-4">
                    <button type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
                    <p th:text="${result}" style="color: red"></p>
                </div>
                <!-- /.col -->
            </div>
        </form>
        <!-- /.social-auth-links -->
        <a href="#">忘记密码?</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <a href="/register" class="text-center">注册</a>
    </div>
    <!-- /.login-box-body -->
</div>

(2)页面展示

springboot整合shiro实现权限控制

8、前台使用shiro结合thymeleaf实现细粒度权限具体实现

<div class="description-block">
    <shiro:hasAnyRoles name="教员">
        <h5 class="description-header" th:text="${courses.size()}">35</h5>
    </shiro:hasAnyRoles>
    <shiro:hasAnyRoles name="管理员">
        <h5 class="description-header" >0</h5>
    </shiro:hasAnyRoles>
    <shiro:hasAnyRoles name="普通用户">
        <h5 class="description-header" >0</h5>
    </shiro:hasAnyRoles>
    <span class="description-text">课程数</span>
</div>

三、注意事项

1、记住我功能

(1)我们自定义记住我过滤器是为了在通过isRemember()登录时,能够得到和通过isAuthenticated()登录一样的信息,因为当我们通过isAuthenticated()登录时,我们获取了一下session信息,通过isRemember()登陆我们只拿到了账户密码信息,虽然能够登陆成功,但是由于缺少一些session信息,在到达首页时会报错,完成不了后续的操作。

(2)在shiroConfig中我们需要配置自定义的过滤器,并在该类的shiro全局配置中把该过滤器注入进来,让其功能实现。

Map<String, Filter> filterMap=new LinkedHashMap<String,Filter>();
filterMap.put("MyRememberFilter",MyRememberFilter());

(3)我们需要把shiroConfig全局配置中的下面的权限改为user权限

linkedHashMap.put("/**", "authc");

如果我们使用authc权限,我们通过记住我登陆成功后,关闭浏览器,再打开还是需要输入账户密码,不能直接到达首页,没有实现效果,因为isRemember()的权限是user权限,比authc权限低,所以通过记住我登陆后,shiro会觉得不安全,让再次输入密码,只有改为user权限才能实现,下次打开浏览器,不需要输入密码,直接登陆成功。

(4)但是使用user权限又会带来新的问题,我们对一些敏感的信息,不想让用户通过user权限看到,这时需要对此种页面使用authc权限,当到达这样的页面时,我们需要账户密码重新登陆一下。

2、密码加密功能

(1)我们必须在MyMathcher中对拿到的前台的密码进行加密后再与数据库中的密码进行比对。

(2)在注册时,我们需要用相同的加密算法对用户注册的密码进行加密保存的数据库中,通过shiro验证时,拿加密后的数据库中的密码与前台用户登录时的密码加密后进行比对。这样才能够实现权限验证。

(3)我只是使用了一种简单的算法,大家可以用加盐的MD5,自行操作。

四、总结

以上是我对shiro的深入理解,希望对小伙伴能有帮助,更多精彩请关注:

 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

https://www.cbedai.net/xander

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/200762.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 数据库表结构设计原则有哪些_数据库表的设计方法

    数据库表结构设计原则有哪些_数据库表的设计方法转载自:http://hi.baidu.com/yzx110/blog/item/0159fadc7b7839a4cd116686.html数据库表结构设计浅谈   这篇文章如题所述,只打算谈一下数据库表本身设计,同时讲到和表结构相关的性能和扩展性问题。下面讲到的东西大多是从实际经验中总结而来,算是对这项技术的一个反思。  基本上在设计数据库表的时候,首先考

  • metasploit指令_msfconsole下载

    metasploit指令_msfconsole下载在MSF里面msfconsole可以说是最流行的一个接口程序。很多人一开始碰到msfconsole的时候就害怕了。那么多复杂的命令语句需要学习,但是msfconsole真的是一个强大的接口程序。Msfconsole提供了一个一体化的集中控制台。通过msfconsole,你可以访问和使用所有的metasploit的插件,payload,利用模块,post模块等等。Msfconsole还有第三方程序的…

  • 详解 JVM Garbage First(G1) 垃圾收集器

    详解 JVM Garbage First(G1) 垃圾收集器版权声明:本文为博主原创文章,转载请联系作者并注明出处。详解JVMGarbageFirst(G1)垃圾收集器前言GarbageFirst(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命。如果使用Java8/9,那么有很大可能希望对G1收集器进行评估。本文详细首先对JVM其他的垃圾收集器进行总结,并与G1进

  • NVL()函数

    NVL()函数NVL()函数是Oracle中的一个函数,NVL()函数的功能是实现空值的转换。NVL()函数的功能是实现空值的转换。例如NVL(string1,replace_with)中:当第一个参数(string1)为空时,返回第二个参数(replace_with);当第一个参数(string1)不为空时,则返回第一个参数(string1)。NVL()函数的第一个参数和第二个参数类型必须相同,或…

  • KeyValuePair 和 Dictionary 的关系和区别「建议收藏」

    KeyValuePair 和 Dictionary 的关系和区别「建议收藏」KeyValuePair 和 Dictionary 的关系1、KeyValuePaira、KeyValuePair是一个结构体(struct);b、KeyValu

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号