SpringBoot整合shiro(一)基础配置

公司项目采用的spring-boot框架。在做用户权限功能的时候准备采用shiro权限框架。前面也考虑过spring家族的spring security安全框架。但是经过网上查询对比最终选择了shiro。因为shiro含有基本的安全控制功能,并且配置更为简单,使用也更加简洁。
首先引入shiro依赖jar包

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--shiro缓存插件-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.2.2</version>
        </dependency>

本次shiro插件缓存功能实现采用的是ehcahe。前面也尝试过使用Redis。但是在配置数据源那块报错,无法解决数据源的问题。所以直接改用了ehcahe。后面如果解决了数据源问题。再发整合Redis的教程。
首先我在系统建立用户权限关系
这里我们需要三张表:
SysUser: 用来存储用户的密码,用户名等等信息。
SysRole: 角色表,存放所有的角色信息
SysAuth:权限表,定义了一些操作访问权限信息。
还有两张关联表(这里我们用JPA自动生成。):
SysUserRole: SysUser和SysRole的关联表。
SysRoleAuth:SysRole和SysAuth的关联表。
这里贴三张表的字段设计

public class SysUser {
    private Integer userId; 
    private String userAccount;//用户账号
    private String userPassword;//用户密码
}
public class SysRole {
    private Integer sysRoleId;
    private Byte sysRoleAva; //角色是否生效
    private String sysRoleDes;//角色描述
    private String sysRoleName;//角色名称
}
public class SysAuth {
    private Integer sysAuthId;
    private String sysAuthCode; //权限编号
    private String sysAuthName; //权限名称
    private String sysAuthUrl; //权限请求的url 例如: user/login
    private String sysAuthPermission; //权限的的名称例如 user:login
    private Byte sysAuthAva; //权限是否有效
    private Byte sysAuthType; //权限类型。菜单还是按钮
    private String sysAuthDes; //权限描述
}

1.配置Realms
Realm是一个Dao,通过它来验证用户身份和权限。这里Shiro不做权限的管理工作,需要我们自己管理用户权限,只需要从我们的数据源中把用户和用户的角色权限信息取出来交给Shiro即可。Shiro就会自动的进行权限判断。在项目包下建一个ShiroRealm类,继承AuthorizingRealm抽象类。

import com.lingjiugis.ocr.domain.SysAuth;
import com.lingjiugis.ocr.domain.SysRole;
import com.lingjiugis.ocr.domain.SysUser;
import com.lingjiugis.ocr.service.SysAuthService;
import com.lingjiugis.ocr.service.SysRoleService;
import com.lingjiugis.ocr.service.UserService;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.shiro.authc.*;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.List;
public class ShiroRealm extends AuthorizingRealm {
    private static Logger logger = LoggerFactory.getLogger(ShiroRealm.class);
    //这里尝试过使用@Autowired 但是发现会报错。这个是spring的注解。如果有知道原因的可以留言。谢谢
    @Resource
    private UserService userService;
    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private SysAuthService authService;
    /**
     * 配置权限 注入权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        System.out.println("--------权限配置-------");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        try {
            //注入角色(查询所有的角色注入控制器)
            List<SysRole> list = sysRoleService.selectRoleByUser(user.getUserId());
            for (SysRole role: list){
                authorizationInfo.addRole(role.getSysRoleName());
            }
            //注入角色所有权限(查询用户所有的权限注入控制器)
            List<SysAuth> sysAuths = authService.queryByUserId(user.getUserId());
            for(SysAuth sysAuth:sysAuths){
                authorizationInfo.addStringPermission(sysAuth.getSysAuthPermission());
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error(ExceptionUtils.getFullStackTrace(e));
        }
        return authorizationInfo;
    }

    /**
     * 用户验证
     * @param token 账户数据
     * @return
     * @throws AuthenticationException 根据账户数据查询账户。根据账户状态抛出对应的异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //获取用户的输入的账号
        String username = (String) token.getPrincipal();
        //这里需注意。看别人的教程有人是这样写的String password = (String) token.getCredentials();
        //项目运行的时候报错,发现密码不正确。后来进源码查看发现将密码注入后。Shiro会进行转义将字符串转换成字符数组。
        //源码:this(username, password != null ? password.toCharArray() : null, false, null);
        //不晓得是否是因为版本的原因,建议使用的时候下载源码进行查看
        String password = new String((char[]) token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        SysUser user = userService.selectByAccount(username);
        if(null == user){
            throw new UnknownAccountException();
        }else {
            if(password.equals(user.getUserPassword())){
                if(0 == user.getUserState()){
                    throw new LockedAccountException();
                }else if (2 == user.getUserState()){
                    throw new DisabledAccountException();
                }else{
                    SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(user,user.getUserPassword().toCharArray(),getName());
                    return authorizationInfo;
                }
            } else {
                throw new IncorrectCredentialsException();
            }
        }
    }
}

2.接下来配置Shiro的关键部分

这里要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。

import com.lingjiugis.ocr.config.GlobalExceptionResolver;
import com.lingjiugis.ocr.filter.ShiroSessionManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("--------------------shiro filter-------------------");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        //注意过滤器配置顺序 不能颠倒
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        //拦截其他所以接口
        filterChainDefinitionMap.put("/**", "authc");
        //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
        shiroFilterFactoryBean.setLoginUrl("/user/unlogin");
        // 登录成功后要跳转的链接 自行处理。不用shiro进行跳转
        // shiroFilterFactoryBean.setSuccessUrl("user/index");
        //未授权界面;
         shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * shiro 用户数据注入
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm(){
        ShiroRealm shiroRealm = new ShiroRealm();
        return shiroRealm;
    }

    /**
     * 配置管理层。即安全控制层
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        return  securityManager;
    }
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    /**
     * 开启shiro aop注解支持 使用代理方式所以需要开启代码支持
     *  一定要写入上面advisorAutoProxyCreator()自动代理。不然AOP注解不会生效
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

3.修改我们的Controller中的登录请求

// 这里如果不写method参数的话,默认支持所有请求,如果想缩小请求范围,还是要添加method来支持get, post等等某个请求。
    @RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
        BaseResponse<String> baseResponse = new BaseResponse<>();
        Subject subject = SecurityUtils.getSubject();
        //数据库的密码我进行了Md5加密。如果没有进行加密的无需这个
        user.setUserPassword(MD5Util.getPwd(user.getUserPassword()));
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserAccount(),user.getUserPassword());
        try {
            subject.login(token);
            //System.out.println(getSession().getId());
            baseResponse.success(getSession().getId());
        } catch (UnknownAccountException e){
            baseResponse.setMsg("用户名不存在");
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            baseResponse.setMsg("密码错误");
        } catch (LockedAccountException e){
            baseResponse.setCode(CodeField.ACCOUNT_NOT_ACTIVAT);
            baseResponse.setMsg(CodeField.ACCOUNT_NOT_ACTIVAT_MSG);
        }catch (DisabledAccountException e){
            baseResponse.setCode(CodeField.ACCOUNT_BAN);
            baseResponse.setMsg(CodeField.ACCOUNT_BAN_MSG);
        } catch (Exception e){
            e.printStackTrace();
            logger.error(ExceptionUtils.getFullStackTrace(e));
        }
        return baseResponse;
    }

这里@RequestMapping之所以没加method是因为如果用户没登录,Shiro会调用get方法请求/login,而后面我们在login页面会用post请求发送form表单,所以这里就没设置method(默认支持所有请求)。
配置完成了就可以运行起来了。

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值