IT教程 ·

SpringBoot&Shiro实现权限治理

Swift--struct与class的区别(汇编角度底层分析)

 

一、数据库模板设想

在本文中,我们运用RBAC(Role-Based Access Control,基于角色的接见掌握)模子设想用户,角色和权限间的关联。简朴地说,一个用户具有多少角色,每个角色具有多少权限。如许,就构形成“用户-角色-权限”的受权模子。在这类模子中,用户与角色之间,角色与权限之间,平常者是多对多的关联。如下图所示:

然后我们在来依据这个模子图,设想数据库表,记得本身增加一点测试数据哦

CREATE TABLE `tb_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;

CREATE TABLE `tb_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '角色称号',
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '形貌',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;

CREATE TABLE `tb_role_permission`  (
  `role_id` int(11) NOT NULL COMMENT '角色id',
  `permission_id` int(11) NOT NULL COMMENT '权限id'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;

CREATE TABLE `tb_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `create_time` datetime(0) DEFAULT NULL,
  `status` int(10) DEFAULT NULL COMMENT '状况',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;

CREATE TABLE `tb_user_role`  (
  `role_id` int(11) NOT NULL COMMENT '角色id',
  `user_id` int(11) NOT NULL COMMENT '用户id'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;

二、Pojo设想

我们建立对应的类,笔者这里用了lombok插件,记得先装置插件

@Data
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private Date createTime;
    private Integer status;
}

@Data
public class Role  implements Serializable {
    private Integer id;
    private String name;
    private String description;
}

@Data
public class Permission implements Serializable {
    private Integer id;
    private String url;
    private String name;
}

三、Dao层设想

由于我们只是做一个演示,只涉及到用户登录,用户角色、权限查找,并未完成过量要领

建立UserMapperRolePermissionMapperUserRoleMapper 三个接口

注重:记得在Mapper接口上面加一个扫描注解@Mapper或许在boot启动类上加一个@MapperScan(value = "mapper包途径")注解

public interface UserMapper {
    @Select("select * from tb_user where username=#{username}")
    User selectByName(String username);
}
--------------------------
    
public interface UserRoleMapper {
    /**
     *
     * 查询用户角色(大概一个用户有多个角色)
     * @param username
     * @return
     */
    @Select("select r.id,r.name,r.description from tb_role r " +
            "left join tb_user_role ur on(r.id = ur.role_id)" +
            "left join tb_user u on(u.id=ur.user_id)" +
            "where u.username =#{username}")
    List<Role> findByUserName(String username);
}

------------------------------------------------
public interface RolePermissionMapper {
    /**
     * 经由过程角色id查询权限
     * @param roleId
     * @return
     */
    @Select("select p.id,p.url,p.name from tb_permission p " +
            "left join tb_role_permission rp on(p.id=rp.permission_id)" +
            "left join tb_role r on(r.id=rp.role_id)" +
            "where r.id=#{roleId}")
    List<Permission> findByRoleId(Integer roleId);
}

四、Shiro整合完成思绪

好了,前面的一些东西,都是能够算是准备工作,如今才是真正入手下手整合Shiro了,我们先来屡一下思绪,完成认证权限功用主要能够归结为3点:

1.定义一个ShiroConfig设置类,设置 SecurityManager Bean , SecurityManager为Shiro的平安治理器,治理着一切Subject;

2.在ShiroConfig中设置 ShiroFilterFactoryBean ,它是Shiro过滤器工场类,依靠SecurityManager ;

3.自定义Realm完成类,包括 doGetAuthorizationInfo()doGetAuthenticationInfo()要领 ,

五、定义ShiroConfig设置类

/**
 * @ClassName ShiroConfig
 * @Description TODO
 * @Author fqCoder
 * @Date 2020/2/29 3:08
 * @Version 1.0
 */
@Configuration
public class ShiroConfig {

    /**
     * 这是shiro的大管家,相当于mybatis里的SqlSessionFactoryBean
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //毛病页面,认证不经由过程跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        //页面权限掌握
        shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap());
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    /**
     * web运用治理设置
     * @param shiroRealm
     * @param cacheManager
     * @param manager
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(Realm shiroRealm, CacheManager cacheManager, RememberMeManager manager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setCacheManager(cacheManager);
        securityManager.setRememberMeManager(manager);//记着Cookie
        securityManager.setRealm(shiroRealm);
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }
    /**
     * session逾期掌握
     * @return
     * @author fuce
     * @Date 2019年11月2日 下昼12:49:49
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager();
        // 设置session逾期时候3600s
        Long timeout=60L*1000*60;//毫秒级别
        defaultWebSessionManager.setGlobalSessionTimeout(timeout);
        return defaultWebSessionManager;
    }
    /**
     * 加密算法
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");//采纳MD5 举行加密
        hashedCredentialsMatcher.setHashIterations(1);//加密次数
        return hashedCredentialsMatcher;
    }

    /**
     * 记着我的设置
     * @return
     */
    @Bean
    public RememberMeManager rememberMeManager() {
        Cookie cookie = new SimpleCookie("rememberMe");
        cookie.setHttpOnly(true);//经由过程js剧本将没法读取到cookie信息
        cookie.setMaxAge(60 * 60 * 24);//cookie保留一天
        CookieRememberMeManager manager=new CookieRememberMeManager();
        manager.setCookie(cookie);
        return manager;
    }
    /**
     * 缓存设置
     * @return
     */
    @Bean
    public CacheManager cacheManager() {
        MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//运用内存缓存
        return cacheManager;
    }

    /**
     * 设置realm,用于认证和受权
     * @param hashedCredentialsMatcher
     * @return
     */
    @Bean
    public AuthorizingRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
        MyShiroRealm shiroRealm = new MyShiroRealm();
        //校验暗码用到的算法
//        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return shiroRealm;
    }

    /**
     * 启用shiro方言,如许能在页面上运用shiro标签
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * 启用shiro注解
     *到场注解的运用,不到场这个注解不见效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

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

注重:(当时笔者碰到的一个小问题,贴出来给人人涨姿态)

注解无效,登录时不会实行考证角色和权限的要领,只会实行登录考证要领,遂查询材料,得知shiro在subject.login(token)要领时不会实行doGetAuthorizationInfo要领,只要在接见到有权限考证的接口时会挪用检察权限,因而猜测注解无效,发明shiro的权限注解须要开启才有效,增加在设置文件中到场advisorAutoProxyCreatorgetAuthorizationAttributeSourceAdvisor两个bean开启shiro注解,解决问题。

六.建立ShiroFilterMapFactory类

注重:

1.这里要用LinkedHashMap 保证有序

2.filterChain基于短路机制,即最早婚配准绳,

3.像anon、authc等都是Shiro为我们完成的过滤器,我给出了一张表,在文章尾附录,自行检察

/**
 * @ClassName ShiroFilterMapFactory
 * @Description TODO
 * @Author fqCoder
 * @Date 2020/2/29 3:09
 * @Version 1.0
 */
public class ShiroFilterMapFactory {

    public static Map<String, String> shiroFilterMap() {
//      设置途径映照,注重这里要用LinkedHashMap 保证有序
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //对一切用户认证
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        //对一切页面举行认证
        filterChainDefinitionMap.put("/**", "authc");
        return filterChainDefinitionMap;
    }
}

设置完了ShiroConfig后,完成本身的Realm,然后注入到SecurityManager里

七、完成自定义Realm类

自定义Realm类须要继续 AuthorizingRealm 类,完成 doGetAuthorizationInfo()和doGetAuthenticationInfo()要领即可 ,

doGetAuthorizationInfo() 要领是举行受权的要领,猎取角色的权限信息

doGetAuthenticationInfo()要领是举行用户认证的要领,考证用户名和暗码

/**
 * @ClassName MyShiroRealm
 * @Description TODO
 * @Author fqCoder
 * @Date 2020/2/29 3:08
 * @Version 1.0
 */
@Service
public class MyShiroRealm  extends AuthorizingRealm {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private RolePermissionMapper rolePermissionMapper;

    /**
     * 猎取用户角色和权限
     * @param principal
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        if(principal == null){
            throw new AuthorizationException("principals should not be null");
        }
        User userInfo= (User) SecurityUtils.getSubject().getPrincipal();
        System.out.println("用户-->"+userInfo.getUsername()+"猎取权限中");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        //用户猎取角色集
        List<Role> roleList=userRoleMapper.findByUserName(userInfo.getUsername());
        Set<String> roleSet=new HashSet<>();
        for (Role r:roleList){
            Integer roleId=r.getId();//猎取角色id
            simpleAuthorizationInfo.addRole(r.getName());//增加角色名字
            List<Permission> permissionList=rolePermissionMapper.findByRoleId(roleId);
            for (Permission p:permissionList){
                //增加权限
                simpleAuthorizationInfo.addStringPermission(p.getName());
            }
        }

        System.out.println("角色为-> " + simpleAuthorizationInfo.getRoles());
        System.out.println("权限为-> " + simpleAuthorizationInfo.getStringPermissions());
        return simpleAuthorizationInfo;
    }

    /**
     * 登录认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //猎取用户输入的用户名暗码
        String username= (String) token.getPrincipal();
        String password=new String((char[])token.getCredentials());

        System.out.println("用户输入--->username:"+username+"-->password:"+password);

        //在数据库中查询
        User userInfo=userMapper.selectByName(username);
        if (userInfo == null) {
            throw new UnknownAccountException("用户名或暗码毛病!");
        }
        if (!password.equals(userInfo.getPassword())) {
            throw new IncorrectCredentialsException("用户名或暗码毛病!");
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, // 用户名
                userInfo.getPassword(), // 暗码
                getName() // realm name
        );
        return authenticationInfo;
    }
}

个中UnknownAccountException等非常为Shiro自带非常,Shiro具有雄厚的运行时AuthenticationException条理构造,能够正确指出尝试失利的缘由。

八、掌握层设想

1.建立一个LoginController.class类

用来处置惩罚登录接见要求

/**
 * @ClassName LoginController
 * @Description TODO
 * @Author fqCoder
 * @Date 2020/2/29 6:06
 * @Version 1.0
 */
@Controller
public class LoginController {

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

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

    @GetMapping("/index")
    public String index(Model model){
        User user= (User) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("user",user);
        return "index";
    }

    @PostMapping("login")
    @ResponseBody
    public AjaxResult login(User user,Boolean rememberMe){
        System.out.println("user = " + user);
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        //猎取Subject 对象
        Subject subject= SecurityUtils.getSubject();
        try {
            if (rememberMe){
                token.setRememberMe(true);
            }
            subject.login(token);
            return AjaxResult.success("/index");
        } catch (UnknownAccountException e) {
            return AjaxResult.error(e.getMessage());
        } catch (IncorrectCredentialsException e) {
            return AjaxResult.error(e.getMessage());
        }
    }
    @GetMapping("/403")
    public String forbid(){
        return "403";
    }
}

2.建立一个UserController.class类

用于处置惩罚User类的接见要求,并运用Shiro权限注解掌握权限:

/**
 * @ClassName UserController
 * @Description TODO
 * @Author fqCoder
 * @Date 2020/3/3 15:14
 * @Version 1.0
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @RequiresPermissions("user:queryAll")
    @GetMapping("/queryAll")
    public String queryAll(){

        //只演示框架...功用不完成
        return "查询列表";
    }

    @RequiresPermissions("user:add")
    @GetMapping("/add")
    public String userAdd(){
        return "增加用户";
    }

    @RequiresPermissions("user:delete")
    @GetMapping("/delete")
    public String userDelete(){
        return "删除用户";
    }
}

九、前端页面设想

1.编写login.html页面

这里我只贴主要代码,详细的代码,到找哦!

<form id="loginForm">
    <input type="text" id="username" name="username" class="text"  />
    <input type="password" id="password" name="password"  />
</form>
<div class="signin">
    <input id="loginBut" type="button" value="Login" >
</div>

-------js代码----
<script type="text/javascript">
    $.fn.serializeObject = function () {
        var o = {};
        var a = this.serializeArray();
        $.each(a, function () {
            if (o[this.name]) {
                if (!o[this.name].push) {
                    o[this.name] = [o[this.name]];
                }
                o[this.name].push(this.value);
            } else {
                o[this.name] = this.value || '';
            }
        });
        return o;
    };

    $(function () {
        $("#loginBut").click(function () {
            var  arr=$('#loginForm').serializeObject();
            $.ajax({
                url: '/login',
                type: 'post',
                data:  arr,
                dataType: "json",
                success: function (data) {
                    if (data.code==200){
                        location.href=data.msg;
                    } else {
                        alert(data.msg);
                    }
                },
                error: function (data) {
                    alert(data.msg);
                }
            })
        });
    });
</script>

当用户登录进来的时刻调到index.html

2.编写index.html页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>番茄迎接您!</h1>
登录用户:【[[${user.username}]]】
<a th:href="@{/logout}">注销</a>


<h2>权限测试</h2>
<a th:href="@{/user/queryAll}">猎取用户悉数信息</a>
<a th:href="@{/user/add}">增加用户</a>
<a th:href="@{/user/delete}">删除用户</a>
</body>
</html>

3.编写403页面

比较简朴,此处能用就行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>403</title>
</head>
<body>
<h1>403权限不够</h1>
<a href="/index">首页</a>
</body>
</html>

十、测试&问题

启动项目:接见http://localhost:8080/,它会自动阻拦,页面重定向到 http://localhost:8080/login ,登录胜利跳转到http://localhost:8080/index

问题:

登录测试用户的时刻,接见没有权限的链接要求时,背景抛出org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method非常

当时以为在ShiroConfig设置类中设置了shiroFilterFactoryBean.setUnauthorizedUrl("/403");

没有权限的要求会自动从定向到/403,然后倒是抛出了非常,厥后在一篇文章中看到了,说这个设置只对filterChain起作用 ,针对这个问题,我们能够定义一个全局非常捕捉类:

@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    public String handleAuthorizationException() {
        return "403";
    }
}

 

附录:

1.Shiro阻拦机制表

Filter Name Class Description
anon 匿名阻拦器,即不须要登录即可接见;平常用于静态资本过滤;示例/static/**=anon
authc 基于表单的阻拦器;如/**=authc,假如没有登录会跳到响应的登录页面登录
authcBasic Basic HTTP身份考证阻拦器
logout 退出阻拦器,主要属性:redirectUrl:退出胜利后重定向的地点(/),示例/logout=logout
noSessionCreation 不建立会话阻拦器,挪用subject.getSession(false)不会有什么问题,然则假如subject.getSession(true)将抛出DisabledSessionException非常
perms 权限受权阻拦器,考证用户是不是具有一切权限;属性和roles一样;示例/user/**=perms["user:create"]
port 端口阻拦器,主要属性port(80):能够经由过程的端口;示例/test= port[80],假如用户接见该页面黑白80,将自动将要求端口改成80并重定向到该80端口,其他途径/参数等都一样
rest rest作风阻拦器,自动依据要求要领构建权限字符串;示例/users=rest[user],会自动拼出user:read,user:create,user:update,user:delete权限字符串举行权限婚配(一切都得婚配,isPermittedAll)
roles 角色受权阻拦器,考证用户是不是具有一切角色;示例/admin/**=roles[admin]
ssl SSL阻拦器,只要要求协定是https才经由过程;不然自动跳转会https端口443;其他和port阻拦器一样;
user 用户阻拦器,用户已身份考证/记着我登录的都可;示例/**=user

【WPF学习】第五十三章 动画类型回顾

参与评论