Spring集成shiro做登陆认证

简介: 一、背景   其实很早的时候,就在项目中有使用到shiro做登陆认证,直到今天才又想起来这茬,自己抽空搭了一个spring+springmvc+mybatis和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做登陆的授权和认证,其实很简单,继续努力成长!

 

相关文章
|
2月前
|
安全 Java 数据库
安全无忧!在 Spring Boot 3.3 中轻松实现 TOTP 双因素认证
【10月更文挑战第8天】在现代应用程序开发中,安全性是一个不可忽视的重要环节。随着技术的发展,双因素认证(2FA)已经成为增强应用安全性的重要手段之一。本文将详细介绍如何在 Spring Boot 3.3 中实现基于时间的一次性密码(TOTP)双因素认证,让你的应用安全无忧。
114 5
|
2月前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
2天前
|
安全 Java 数据安全/隐私保护
基于内存认证的 Spring Security
通过本文的介绍,希望您能够深入理解基于内存认证的Spring Security配置与使用方法,并能够在实际开发中灵活应用这一技术,提升应用的安全性和用户体验。
25 9
|
11天前
|
XML Java API
Spring Boot集成MinIO
本文介绍了如何在Spring Boot项目中集成MinIO,一个高性能的分布式对象存储服务。主要步骤包括:引入MinIO依赖、配置MinIO属性、创建MinIO配置类和服务类、使用服务类实现文件上传和下载功能,以及运行应用进行测试。通过这些步骤,可以轻松地在项目中使用MinIO的对象存储功能。
|
13天前
|
消息中间件 Java Kafka
什么是Apache Kafka?如何将其与Spring Boot集成?
什么是Apache Kafka?如何将其与Spring Boot集成?
44 5
|
17天前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
46 8
|
15天前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
24 1
|
16天前
|
消息中间件 监控 Java
您是否已集成 Spring Boot 与 ActiveMQ?
您是否已集成 Spring Boot 与 ActiveMQ?
31 0
|
2月前
|
人工智能 JavaScript 网络安全
ToB项目身份认证AD集成(三完):利用ldap.js实现与windows AD对接实现用户搜索、认证、密码修改等功能 - 以及针对中文转义问题的补丁方法
本文详细介绍了如何使用 `ldapjs` 库在 Node.js 中实现与 Windows AD 的交互,包括用户搜索、身份验证、密码修改和重置等功能。通过创建 `LdapService` 类,提供了与 AD 服务器通信的完整解决方案,同时解决了中文字段在 LDAP 操作中被转义的问题。
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
151 1