Spring集成shiro做登陆认证

一、背景

  其实很早的时候,就在项目中有使用到shiro做登陆认证,直到今天才又想起来这茬,自己抽空搭了一个spring+springmvc+mybatis和shiro进行集成的种子项目,当然里面还有很简单的测试。本文将讲述在maven下如何进行集成,希望对你有所帮助,喜欢请推荐。至于shiro相关的,最近也会写几篇介绍的,希望能够有一个主观的了解。

二、集成步骤

  说明:关于spring+springmvc+mybatis的集成请移步另一篇博客:Spring+SpringMvc+Mybatis框架集成搭建教程

  1.第一步引入shiro依赖

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-core</artifactId>
   <version>1.2.3</version>
</dependency>
<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.2.3</version>
</dependency>

  2.在web.xml中引入shiro的filter

<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
     <filter-name>shiroFilter</filter-name>
     <url-pattern>/*</url-pattern>
</filter-mapping>

  3.resources文件下的spring目录下新建spring-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd">

    <description>Shiro Configuration</description>

    <!--custom myself realm-->
    <bean id="customRealm" class="com.hafiz.www.shiro.CustomRealm"/>

    <!--Shiro`s main business-tier object for web-enable applications-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm"/>
    </bean>

    <!--shiro filter-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.html"/>
        <property name="successUrl" value="/index.html"/>
        <property name="unauthorizedUrl" value="/unauthorized.html"/>
        <property name="filters">
            <util:map>
                <entry key="auth">
                    <bean class="com.hafiz.www.filter.AuthorizeFilter"/>
                </entry>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /login.json = anon
                /logout.json = anon
                /js/** = anon
                / = authc
                /** = auth
            </value>
        </property>
    </bean>
</beans>

  4.新建自定义的Realm,CustomRealm.java

package com.hafiz.www.shiro;

import com.hafiz.www.po.UserEntity;
import com.hafiz.www.service.UserService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
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 java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Desc:自定义Realm
 * Created by hafiz.zhang on 2017/7/21.
 */
public class CustomRealm extends AuthorizingRealm{

    private static final String _WILDCARD = "*";
    private static final String _PATTERN_APPEND = "+.*";

    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 如果项目不需要授权,则该方法直接 return null;
        UserEntity operator = (UserEntity) principalCollection.getPrimaryPrincipal();
        //获取该用户具有的所有的角色资源(把null换成findResourceUrlById())
        List<String> resourceList = null;
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> allPermissions = new HashSet<>(resourceList);
        allPermissions.remove("");
        allPermissions.remove(null);
        List<String> patternPermissions = new ArrayList<>();
        //通配url,以*,或者.*
        if (CollectionUtils.isNotEmpty(allPermissions)) {
            for (String per : allPermissions) {
                if (per.endsWith(_WILDCARD)) {
                    patternPermissions.add(per);
                }
            }
        }
        if (CollectionUtils.isNotEmpty(patternPermissions)) {
            allPermissions.removeAll(patternPermissions);
            for (String pat : patternPermissions) {
                if(pat.endsWith(_WILDCARD)){
                    info.addObjectPermission(new CustomRegexPermission(pat.substring(0,pat.length()-1)+_PATTERN_APPEND));
                }else{
                    info.addObjectPermission(new CustomRegexPermission(pat+_PATTERN_APPEND));
                }
            }
        }
        info.addStringPermissions(allPermissions);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        List<UserEntity> users = userService.getByUserName(username);
        if (CollectionUtils.isEmpty(users)) {
            throw new UnknownAccountException();
        }
        if (users.size() != 1) {
            throw new LockedAccountException("用户名重复,请联系技术");
        }
        UserEntity user = users.get(0);
        username = user.getUserName();
        String password = user.getPassword();
        // 第一个参数也可以放user对象,这样在doGetAuthorizationInfo()方法中可以直接使用
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

说明:doGetAuthorizationInfo()是做授权,比如项目中有很多资源,指定角色的人员只有指定的资源,这种情况可以使用这个方法来做授权,doGetAuthenticationInfo()方法做认证,我们一般是用作用户登陆主逻辑,这个方法中我们只需要根据用户提供的用户名去数据库中查找对应的用户信息,然后用该信息返回一个SimpleAuthenticationInfo对象即可,不需要比较数据库中的密码和token中的密码是否一直,因为在登陆时shiro会帮我们做这件事,不匹配会抛出IncorrectCredentialsException来提示密码错误。

  5.自定义AuthroizeFilter.java

package com.hafiz.www.filter;

import com.alibaba.fastjson.JSON;
import com.hafiz.www.consts.AppConst;
import com.hafiz.www.vo.JsonResult;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.Writer;

/**
 * Desc:认证验证过滤器
 * Created by hafiz.zhang on 2017/7/21.
 */
public class AuthorizeFilter extends AuthorizationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        Subject subject = getSubject(request, response);
        if (!subject.isAuthenticated()) {
            String requestURI = request.getRequestURI();
            if (requestURI.endsWith(".json")) {
                JsonResult jr = new JsonResult();
                jr.setCode(AppConst.UNAUTHORIZED);
                jr.setMsg("登陆超时,请重新登录");
                response.setStatus(AppConst.UNAUTHORIZED);
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json;charset=UTF-8");
                Writer w = response.getWriter();
                w.write(JSON.toJSONString(jr));
                w.flush();
                w.close();
            } else {
                response.sendRedirect(request.getContextPath() + "/login.html");
            }
            return false;
        }
        Boolean isAjax = isAjax(request);
        if (subject.getPrincipal() != null && isAjax) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(AppConst.UNAUTHORIZED);
            Writer w = response.getWriter();
            JsonResult jr = new JsonResult();
            jr.setCode(AppConst.UNAUTHORIZED);
            jr.setMsg("无权限操作!");
            w.write(JSON.toJSONString(jr));
            w.flush();
            w.close();
            return  false;
        }
        return super.onAccessDenied(servletRequest, servletResponse);
    }

    /**
     * 根据请求头判断是不是ajax请求
     *
     * @param request 请求实体
     *
     * @return
     */
    private Boolean isAjax(HttpServletRequest request) {
        Boolean isAjax = request.getHeader("X-Requested-With") != null
                            && "XMLHttpRequest".equals( request.getHeader("X-Requested-With").toString());
        return isAjax;
    }

    /**
     * 判断用户是否可以访问请求的资源
     *
     * @param request 用户请求
     *
     * @param response 响应
     *
     * @param mappedValue
     *
     * @return
     *
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,
                                      Object mappedValue) throws Exception {
        // 登陆请求直接放行
        if (isLoginRequest(request, response)) {
            return true;
        }

        // 获取用户认证信息
        Subject subject = getSubject(request, response);
        if (!subject.isAuthenticated()) {
            HttpServletRequest httpServletRequest = (HttpServletRequest)request;
            HttpSession session = httpServletRequest.getSession();
            String requestUrl = httpServletRequest.getRequestURL().toString();
            session.setAttribute(AppConst.LAST_URL_KEY, requestUrl);
            return false;
        }

        // 判断请求资源是否授权(如果项目不需要授权,下面省略,直接return true,否则加上下面注释掉的代码,然后最后return false;)
        /*String resource = getPathWithinApplication(request);
        if (subject.isPermitted(resource)) {
            return true;
        }*/
        return true;
    }
}

6.自定义SessionUtils.java来管理shiro相关的session等

package com.hafiz.www.shiro;

import com.hafiz.www.po.UserEntity;
import com.hafiz.www.vo.JsonResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Desc: 用户Session工具类
 * Created by hafiz.zhang on 2017/7/22.
 */
public class SessionUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(SessionUtils.class);

    /**
     * 获取登陆的key,即用户名
     *
     * @return
     */
    public static String getLoginKey() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            return (String)subject.getPrincipal();
        }
        return null;
    }

    /**
     * 获取当前登陆用户
     *
     * @return
     */
    public static UserEntity getLoginUser() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            Session session = subject.getSession();
            Object loginUser = session.getAttribute("loginUser");
            return loginUser == null ? null : (UserEntity)loginUser;
        }
        return null;
    }

    /**
     * 获取当前登陆用户id
     *
     * @return
     */
    public static Long getLoginUserId() {
        UserEntity user = getLoginUser();
        if (user != null) {
            return user.getId();
        }
        return null;
    }

    /**
     * 获取当前用户是否登陆
     *
     * @return
     */
    public static Boolean isLoggedIn() {
        boolean isLoggedIn = SecurityUtils.getSubject().isAuthenticated();
        return isLoggedIn;
    }

    public static JsonResult login(String userName, String password) {
        Subject subject = SecurityUtils.getSubject();
        AuthenticationToken token = new UsernamePasswordToken(userName, password);
        JsonResult jr = new JsonResult();
        try {
            subject.login(token);
        } catch (UnknownAccountException ue) {
            LOGGER.error("用户不存在:{}", userName);
            jr.setSuccess(false);
            jr.setMsg("没有该账号");
        } catch (LockedAccountException le) {
            LOGGER.error("用户名重复");
            jr.setSuccess(false);
            jr.setMsg("用户名重复,请联系技术");
        } catch (IncorrectCredentialsException ie) {
            LOGGER.error("密码错误");
            jr.setSuccess(false);
            jr.setMsg("密码错误");
        } catch (Exception e) {
            LOGGER.error("登陆出错:{}", e.getLocalizedMessage());
            jr.setSuccess(false);
            jr.setMsg("登陆出错:" + e.getLocalizedMessage());
        }
        return jr;
    }

    /**
     * 用户退出登陆
     */
    public static void logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }
}

经过上面这些步骤,我们就完成了spring和shiro的集成,关于简单的测试程序,不再贴出,在这里提供该种子项目的github地址:https://github.com/hafizzhang/spring-shiro.git

三、总结

  通过本文,我们就完成了spring集成shiro做登陆的授权和认证,其实很简单,继续努力成长!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Android 应用安装过程分析

在之前的文章中,我们对PakageManagerService启动流程分析 做了简单的介绍,并对PMS系统的启动流程做了详细的解析。上面只是说到了Android...

78590
来自专栏纯洁的微笑

springboot(十八):使用Spring Boot集成FastDFS

上篇文章介绍了《如何使用Spring Boot上传文件》,这篇文章我们介绍如何使用Spring Boot将文件上传到分布式文件系统FastDFS中。 这个项目会...

53640
来自专栏个人分享

Spark代码调优(一)

import org.apache.spark.sql.{DataFrame, Row, SQLContext}

25610
来自专栏

Flex 调用添加了SoapHeader的web service

using System.Web.Services; using System.Web.Services.Protocols;

11320
来自专栏乐沙弥的世界

基于RMAN从活动数据库异机克隆(rman duplicate from active DB)

      Oracle 11g RMAN能够实现基于活动数据库进行异机克隆,从而省去需要先备份再ftp到辅助服务器的过程。这一切可以全部交给Oracle来搞定...

12530
来自专栏Java技术分享

第八章:Shiro和Spring的集成——深入浅出学Shiro细粒度权限开发框架

Standalone Applications nShiro 应用程序需要一个具有单例SecurityManager 实例的应用程序。请注意,这不会是一个静态的...

64450
来自专栏dalaoyang

使用shiro安全管理

20630
来自专栏Java技术分享

第八章:Shiro和Spring的集成——深入浅出学Shiro细粒度权限开发框架

Standalone Applications

20680
来自专栏BinarySec

一些pwn题目的解题思路[pwnable.kr] II

目录 以下是solution的目录 #mistake #shellshock #coin1 #blackjack #lotto #cmd1 Other 一些pw...

40550
来自专栏Android 研究

APK安装流程详解14——PMS中的新安装流程上(拷贝)补充

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACK...

39010

扫码关注云+社区

领取腾讯云代金券