SpringBoot整合Shiro权限管理

1.Shiro简简简简介

Apache Shiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。

image.png

可以看到Shiro的主要体系结构:
1、 Authentication 认证 ---- 用户登录
2、 Authorization 授权 --- 用户具有哪些权限
3、 Cryptography 安全数据加密
4、 Session Management 会话管理
5、 Web Integration web系统集成
6、 Interations 集成其它应用,spring、缓存框架

下面我主要讲前两个,认证和授权的用法。】

2.建立SpringBoot应用

声明:项目使用了SpringBoot、mybatis plus、RBAC的5张表(注意:我这里的系统默认一个用户只有一个角色)关于这几部门不明白的,请自行百度。

2.1项目引入的maven依赖有:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.1.RELEASE
         
    
    com.yunqing
    questionnaire
    0.0.1-SNAPSHOT
    questionnaire
    Demo project for Spring Boot

    
        1.8
    

    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.2.0
        
        
            com.baomidou
            mybatis-plus-generator
            3.2.0
        
        
            org.freemarker
            freemarker
            2.3.29
        
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
        
            org.apache.shiro
            shiro-spring
            1.4.1
        
        
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        
        
        
            mysql
            mysql-connector-java
        
        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


2.2创建LoginController首先令/user/index访问到主页面

package com.yunqing.questionnaire.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Created by yunqing on 2019/11/20 23:33
 */
@Controller
@RequestMapping("/user")
public class LoginController {

    @GetMapping("/index")
    public String index() {
        return "index";
    }

}

springboot默认h5页面放在templates中


image.png

这样访问/user/index就能访问到上图的index.html页面了。

3.SpringBoot整合Shiro实现用户认证

3.1 分析Shiro的核心Api

Subject: 用户主体(把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm:Shiro连接数据的桥梁

3.2 SpringBoot整合Shiro

3.2.1自定义realm类

/**
 * 自定义Realm
 * Created by yunqing on 2019/11/21 21:43
 */
@Slf4j
public class UserRealm extends AuthorizingRealm {

    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("执行授权逻辑");
    }

    /**
     * 执行认证逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("执行认证逻辑");

    }
}

3.2.2编写Shiro配置类ShiroConfig

/**
 * Created by yunqing on 2019/11/21 21:39
 */
@Configuration
public class ShiroConfig {


    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        
        return shiroFilterFactoryBean;
    }


    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }


}

3.3使用Shiro内置过滤器实现页面拦截

/**
 * Created by yunqing on 2019/11/21 21:39
 */
@Configuration
public class ShiroConfig {


    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * 添加shiro内置过滤器
         * 常用的过滤器
         * anon:无需认证登录就可以访问
         * authc:必须认证才能访问
         * user:如果使用remeberMe的功能可以直接访问
         * perms:该资源必须得到资源权限才能访问
         * role:该资源必须得到角色权限才能访问
         */
        Map filterMap = new LinkedHashMap<>();

        filterMap.put("/user/index", "anon");//设置/user/index不需要认证
        filterMap.put("/user/add", "authc");//设置/user/add需要认证
        //filterMap.put("/user/*", "authc");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //Shiro默认需要认证跳转login.jsp,这里更改通过controller跳转到login.html
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
        //shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");
        return shiroFilterFactoryBean;
    }


    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }


}

上面的代码运行后,首先localhost:8080/user/index进入index.html我们设置了不拦截,index.html页面如下


    跳转添加页面
    跳转修改页面

这时候的LoginController页面也加入了这几个跳转的方法

@GetMapping("/index")
    public String index() {
        return "index";
    }
    @GetMapping("/add")
    public String add() {
        return "add";
    }
    @GetMapping("/update")
    public String update() {
        return "update";
    }
    @GetMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

如上面设置/user/add需要认证,/user/update没设置认证

image.png

image.png

image.png

3.4实现用户认证(登录)操作

3.4.1 登录页面login.html




    
    Title


登录页面login

用户名:
密码:

3.4.2 编写LoginController中的登录逻辑

@GetMapping("/login")
    public String login(String username, String password, Model model) {
        System.out.println(username);
        /**
         * 1.获取Subject
         */
        Subject subject = SecurityUtils.getSubject();

        /**
         * 把用户名,密码封装进UsernamePasswordToken
         */
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try{
            /**
             * subject携带token调用login,不出异常则登陆成功
             * 具体登录去ShiroConfig的认证里去决定
             */
            subject.login(token);
            //登陆成功
            return "redirect:index";//重定向到index页面

        } catch (IncorrectCredentialsException e) {
            //e.printStackTrace();
            //登录失败:密码错误
            model.addAttribute("msg", "密码错误");
            return "login";
        } catch (UnknownAccountException e) {
            //e.printStackTrace();
            //登录失败:用户名不存在
            model.addAttribute("msg", "用户名不存在");
            return "login";
        }

    }

3.4.3编写自定义realm的认证逻辑,去数据库查询用户名密码

/**
     * 执行认证逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("执行认证逻辑");
        
        //通过authenticationToken获取当前登录用户信息
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //mybatis plus 去数据库根据登录名查询User
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("login_name", token.getUsername());
        User user = userService.getOne(wrapper);
        //判断是否查询到
        if (StringUtils.isEmpty(user)) {
            return null; //shiro内部会抛出UnknownAccountException异常
        }
        //判断密码,如果认证成功,则第一个参数是传给授权逻辑的,相当于授权逻辑中的Principal()
        return new SimpleAuthenticationInfo(user, user.getPassWord, "");

    }

登录成功后重定向到index.html再次点击跳转添加页面,成功

image.png

测试一下登录过程中用户名不存在,和密码错得情况,看看利用Model返回的msg,login.html页面的thymeleaf标签

是否接收到msg的值。

image.png

image.png

4.实现用户授权

4.1 使用shiro内置过滤器拦截资源

/**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * 添加shiro内置过滤器
         * 常用的过滤器
         * anon:无需认证登录就可以访问
         * authc:必须认证才能访问
         * user:如果使用remeberMe的功能可以直接访问
         * perms:该资源必须得到资源权限才能访问
         * role:该资源必须得到角色权限才能访问
         */
        Map filterMap = new LinkedHashMap<>();

        filterMap.put("/user/index", "anon");//路径/user/index不需要认证
        filterMap.put("/user/login", "anon");//路径/user/login不需要认证
        filterMap.put("/user/add", "perms[user:add]");//路径/user/add不仅需要登录,还需要登录的角色拥有访问授权字符串为user:add资源的权利
        //filterMap.put("/user/update", "perms[user:update]");
        filterMap.put("/user/*", "authc");//拦截/user/*的路径需要进行认证,不需要认证的写在上面了


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");//登录后如果没有访问权限,设置跳转noAuth.html页面
        return shiroFilterFactoryBean;
    }

4.2 完成资源的授权,修改UserRealm类

@Autowired
    private UserService userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RoleResourceService roleResourceService;
    @Autowired
    private ResourceService resourceService;
    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("执行授权逻辑");
        /**
         * 给user:add进行授权
         */
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //SecurityUtils.getSubject() 获取当前user
        //info.addStringPermission("user:add");
        //Subject subject = SecurityUtils.getSubject();
        User user = (User) principalCollection.getPrimaryPrincipal();//获取当前user
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", user.getId());
        UserRole userRole = userRoleService.getOne(wrapper);//获取角色id 当前系统默认一个用户对应一个角色。
        if (StringUtils.isEmpty(userRole)) return null;

        String roleId = userRole.getRoleId();
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_id", roleId);
        List list = roleResourceService.list(queryWrapper);//查出角色对应的资源id
        List strs = new ArrayList<>();
        list.forEach(e->strs.add(e.getResourceId()));
        Set flags = new HashSet<>();
        strs.forEach(e->{
            String flag = resourceService.getById(e).getFlag();
            if (flag!=null && !"".equals(flag))
            flags.add(flag);
        });

        info.addStringPermissions(flags);//授权当前角色可以访问的资源
        return info;
    }
image.png

5.设置页面只显示当前角色能访问的资源thymeleaf整合shiro

5.1 配置ShiroDialect,在ShiroConfig中配置

    /**
     * 使前端shiro-thymeleaf生效
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

5.2 在页面上使用shiro标签,index.html页面




    
    Title







注意,因为加入了shiro授权标签,所以最初访问/user/index就不会显示内容,因为没有被授权,这时候,我们可以直接通过/user/toLogin访问login页面进行登录,如下所示:数据库中admin配置了添加user:add资源授权,admin111配置了修改user:update资源授权。

image.png
image.png

image.png

image.png

你可能感兴趣的:(SpringBoot整合Shiro权限管理)