springboot整合shiro安全框架

一、shiro简介

  • 概述

    apache shiro 是java的一个轻量级的安全框架,功能没有spring security全面,但对于一般的的项目已经足够。其最大的优点是易于上手,所以大多的公司都会用shiro.

    Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等

  • 组件

Subject:主体,可以是任何能与应用交互的 “用户”;

SecurityManager:相当于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的, Shiro 提供默认,但也可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有 1 个或多个 Realm,用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

SessionManager:SessionManager管理Session 的生命周期;Shiro 抽象了一个自己的 Session 来管理主体与应用之间交互的数据;

SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。

二、使用示例

1.引入依赖

<!--shiro-->
<dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring</artifactId>
     <version>1.4.0</version>
</dependency>
<!--简化bean-->
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <optional>true</optional>
</dependency>
2.编写实体类
  1. user.java(用户实体类)

    package top.xtslife.security.bean;
    
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Set;
    
    /**
     * @Author 小涛
     * @Create 2019-08-20  11:12
     */
    @Data
    public class User implements Serializable {
        private String id;
        private String userName;
        private String password;
        /**
         * 用户对应的角色集合
         */
        private Set<Role> roles;
    
        public User() {
        }
    
        public User(String id, String userName, String password, Set<Role> roles) {
            this.id = id;
            this.userName = userName;
            this.password = password;
            this.roles = roles;
        }
    }
    
  2. Role.java(角色对应实体类)

    package top.xtslife.security.bean;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    import java.util.Set;
    
    /**
     * @Author 小涛
     * @Create 2019-08-20  11:14
     */
    @Data
    public class Role implements Serializable {
    
        private String id;
        private String roleName;
        /**
         * 角色对应权限集合
         */
        private Set<Permissions> permissions;
    
        public Role() {
        }
    
        public Role(String id, String roleName, Set<Permissions> permissions) {
            this.id = id;
            this.roleName = roleName;
            this.permissions = permissions;
        }
    }
    
  3. Permissions.java(权限对应实体类)

    package top.xtslife.security.bean;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * @Author 小涛
     * @Create 2019-08-20  11:14
     */
    @Data
    public class Permissions implements Serializable {
        private String id;
        private String permissionsName;
    
        public Permissions() {
        }
    
        public Permissions(String id, String permissionsName) {
            this.id = id;
            this.permissionsName = permissionsName;
        }
    }
    
  4. 编写LoginService

    package top.xtslife.security.service;
    
    import top.xtslife.security.bean.User;
    
    /**
     * @Author 小涛
     * @Create 2019/8/20 11:31
     */
    public interface LoginService {
         User getUserByName(String getMapByName);
    }
    
  5. 编写实现类

    package top.xtslife.security.service.impl;
    
    /**
     * @Author 小涛
     * @Create 2019-08-20  11:30
     */
    
    import org.springframework.stereotype.Service;
    import top.xtslife.security.bean.Permissions;
    import top.xtslife.security.bean.Role;
    import top.xtslife.security.bean.User;
    import top.xtslife.security.service.LoginService;
    
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    @Service
    public class LoginServiceImpl implements LoginService {
    
        @Override
        public User getUserByName(String getMapByName) {
            //模拟数据库查询,正常情况此处是从数据库或者缓存查询。
            return getMapByName(getMapByName);
        }
    
        /**
         * 模拟数据库查询
         * @param userName
         * @return
         */
        private User getMapByName(String userName){
            //共添加两个用户,两个用户都是admin一个角色,
            //wsl有query和add权限,zhangsan只有一个query权限
            Permissions permissions1 = new Permissions("1","query");
            Permissions permissions2 = new Permissions("2","add");
            Set<Permissions> permissionsSet = new HashSet<>();
            permissionsSet.add(permissions1);
            permissionsSet.add(permissions2);
            Role role = new Role("1","admin",permissionsSet);
            Set<Role> roleSet = new HashSet<>();
            roleSet.add(role);
            User user = new User("1","wsl","123456",roleSet);
            Map<String ,User> map = new HashMap<>();
            map.put(user.getUserName(), user);
    
            Permissions permissions3 = new Permissions("3","query");
            Set<Permissions> permissionsSet1 = new HashSet<>();
            permissionsSet1.add(permissions3);
            Role role1 = new Role("2","user",permissionsSet1);
            Set<Role> roleSet1 = new HashSet<>();
            roleSet1.add(role1);
            User user1 = new User("2","zhangsan","123456",roleSet1);
            map.put(user1.getUserName(), user1);
            return map.get(userName);
        }
    }
    
  6. 自定义 Realm:用于查询用户的角色和权限信息并保存到权限管理器

    package top.xtslife.security.realm;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    import top.xtslife.security.bean.Permissions;
    import top.xtslife.security.bean.Role;
    import top.xtslife.security.bean.User;
    import top.xtslife.security.service.LoginService;
    
    /**
     * @Author 小涛
     * @Create 2019-08-20  11:45
     */
    public class CustomRealm extends AuthorizingRealm {
    
        @Autowired
        private LoginService loginService;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取登录用户名
            String name = (String) principalCollection.getPrimaryPrincipal();
            //根据用户名去数据库查询用户信息
            User user = loginService.getUserByName(name);
            //添加角色和权限
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            for (Role role : user.getRoles()) {
                //添加角色
                simpleAuthorizationInfo.addRole(role.getRoleName());
                //添加权限
                for (Permissions permissions : role.getPermissions()) {
                    simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
                }
            }
            return simpleAuthorizationInfo;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //加这一步的目的是在Post请求的时候会先进认证,然后在到请求
            if (authenticationToken.getPrincipal() == null) {
                return null;
            }
            //获取用户信息
            String name = authenticationToken.getPrincipal().toString();
            User user = loginService.getUserByName(name);
            if (user == null) {
                //这里返回后会报出对应异常
                return null;
            } else {
                //这里验证authenticationToken和simpleAuthenticationInfo的信息
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
                return simpleAuthenticationInfo;
            }
        }
    }
    
  7. 写javaconfig:把 CustomRealm 和 SecurityManager 等加入到 spring 容器
    package top.xtslife.security.config;
    
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import top.xtslife.security.realm.CustomRealm;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class ShiroConfig {
        //不加这个注解不生效,具体不详
        @Bean
        @ConditionalOnMissingBean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
            defaultAAP.setProxyTargetClass(true);
            return defaultAAP;
        }
    
        //将自己的验证方式加入容器
        @Bean
        public CustomRealm myShiroRealm() {
            CustomRealm customRealm = new CustomRealm();
            return customRealm;
        }
    
        //权限管理,配置主要是Realm的管理认证
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
    
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, String> map = new HashMap<>();
            //登出
            map.put("/logout", "logout");
            //对所有用户认证
            map.put("/**", "authc");
            //登录
            shiroFilterFactoryBean.setLoginUrl("/login");
            //首页
            shiroFilterFactoryBean.setSuccessUrl("/index");
            //错误页面,认证不通过跳转
            shiroFilterFactoryBean.setUnauthorizedUrl("/error");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            return shiroFilterFactoryBean;
        }
    
        //加入注解的使用,不加入这个注解不生效
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    
  8. 编写controller

    package top.xtslife.security.controller;
    
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationException;
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.apache.shiro.subject.Subject;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import top.xtslife.security.bean.User;
    
    @RestController
    @Api(tags ="LoginController",description = "用户登录管理")
    public class LoginController {
    
        @RequestMapping("/login")
        @ApiOperation("用户登录")
        public String login(User user) {
            //添加用户认证信息
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                    user.getUserName(),
                    user.getPassword()
            );
            try {
                //进行验证,这里可以捕获异常,然后返回对应信息
                subject.login(usernamePasswordToken);
    //            subject.checkRole("admin");
    //            subject.checkPermissions("query", "add");
            } catch (AuthenticationException e) {
                e.printStackTrace();
                return "账号或密码错误!";
            } catch (AuthorizationException e) {
                e.printStackTrace();
                return "没有权限";
            }
            return "login success";
        }
        //注解验角色和权限
        @RequiresRoles("admin")
        @RequiresPermissions("add")
        @RequestMapping("/index")
        @ApiOperation("通过验证访问主页面")
        public String index() {
            return "index!";
        }
    }
    
  9. 编写异常处理

    package top.xtslife.security.component;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.authz.AuthorizationException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @ControllerAdvice
    @Slf4j
    public class MyExceptionHandler {
    
        @ExceptionHandler
        @ResponseBody
        public String ErrorHandler(AuthorizationException e) {
            log.error("没有通过权限验证!", e);
            return "没有通过权限验证!";
        }
    }
    
posted @ 2019-08-20 15:34  SweetLove  阅读(263)  评论(0编辑  收藏  举报