AdminEAP框架-集成Shiro安全认证

news/2024/10/23 12:26:55/

1、概述


AdminEAP

AdminEAP为本人基于AdminLTE改造的后台管理框架,包含了基本的系统管理功能和各种交互demo,项目已经开源到Github,并部署到阿里云。

Github : https://github.com/bill1012/AdminEAP

AdminEAP DEMO: http://www.admineap.com

本文介绍在AdminEAP框架下集成Shiro的配置和核心代码,集成shiro之后,把系统的认证和鉴权给shiro管理。下图为集成之后的登录界面。

AdminEAP登录界面

2、Shiro简介


Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。这不就是我们想要的嘛,而且Shiro的API也是非常简单;其基本功能点如下图所示:

Shiro

  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

更详细的说明在这里不展开,可参考博客 第一章 Shiro简介——《跟我学Shiro》

3、具体实现


AdminEAP使用了Spring框架,所以是Spring集成Shiro

3.1 web.xml配置

放在web.xml前面,让框架扫描到

<filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>

3.2 spring-shiro.xml的配置文件(重点)

这个配置文件需要web.xml引用到,在AdminEAP中,通过以下xml片段引用了所有的spring的配置。

    <context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring*.xml</param-value></context-param>

spring-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/data/jpa"><description>Shiro Configuration</description><!-- 缓存管理--><bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean><!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java --><bean id="adminRealm" class="com.cnpc.framework.filter.SystemAuthorizingRealm" /><!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session --><!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 --><!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="adminRealm" /><property name="cacheManager" ref="cacheManager"/></bean><!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 --><!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!-- Shiro的核心安全接口,这个属性是必须的 --><property name="securityManager" ref="securityManager" /><!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.html"页面 --><property name="loginUrl" value="/login" /><!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) --><!-- <property name="successUrl" value="/system/main"/> --><!-- 用户访问未对其授权的资源时,所显示的连接 --><!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp --><property name="unauthorizedUrl" value="/login" /><!-- Shiro连接约束配置,即过滤链的定义 --><!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --><!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --><!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --><!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --><property name="filterChainDefinitions"><value><!--登录界面可匿名-->/login=anon<!--注销可匿名-->/logout=anon<!--静态资源可匿名-->/resources/**=anon<!--其他要认证-->/**=authc  </value></property></bean><!-- 保证实现了Shiro内部lifecycle函数的bean执行 --><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /><!-- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 --><!-- 配置以下两个bean即可实现此功能 --><!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run --><!-- 由于本例中并未使用Shiro注解,故注释掉这两个bean(个人觉得将权限通过注解的方式硬编码在程序中,查看起来不是很方便,没必要使用) --><!-- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/></bean> -->
</beans> 

3.3 自定义Realm的SystemAuthorizingRealm.java的实现

package com.cnpc.framework.filter;import com.cnpc.framework.base.entity.User;
import com.cnpc.framework.base.service.FunctionService;
import com.cnpc.framework.base.service.RoleService;
import com.cnpc.framework.base.service.UserService;
import com.cnpc.framework.utils.PropertiesUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
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.stereotype.Service;import javax.annotation.Resource;
import java.util.Set;/*** @author billjiang qq:475572229* 系统安全认证实现类*/
@Service
public class SystemAuthorizingRealm extends AuthorizingRealm {/*** 认证回调函数, 登录时调用*/@Resourceprivate UserService userService;@Resourceprivate RoleService roleService;@Resourceprivate FunctionService functionService;/*** 用户认证** @param authcToken 含登录名密码的信息* @return 认证信息*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {if (authcToken == null)throw new AuthenticationException("parameter token is null");UsernamePasswordToken token = (UsernamePasswordToken) authcToken;// 校验用户名密码String password=String.copyValueOf(token.getPassword());User user= userService.getUserByLoginName(token.getUsername());if (user!=null) {if(!password.equals(user.getPassword())&& isNeedPassword()){throw new IncorrectCredentialsException();}// 注意此处的返回值没有使用加盐方式,如需要加盐,可以在密码参数上加return new SimpleAuthenticationInfo(user, token.getPassword(), token.getUsername());}throw new UnknownAccountException();}/*** 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用 shiro 权限控制有三种* 1、通过xml配置资源的权限* 2、通过shiro标签控制权限* 3、通过shiro注解控制权限* 当调用subject.hasRole  subject.isPermitted 或者shiro注解/标签的时候会调用本方法,获取的数据如果配置了缓存会存在缓存中*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {if (principals == null) {throw new AuthorizationException("parameters principals is null");}//获取已认证的用户名(登录名)String username=(String)super.getAvailablePrincipal(principals);Set<String> roleCodes=roleService.getRoleCodeSet(username);Set<String> functionCodes=functionService.getFunctionCodeSet(roleCodes);SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();authorizationInfo.setRoles(roleCodes);authorizationInfo.setStringPermissions(functionCodes);return authorizationInfo;}//是否需要校验密码登录,用于开发环境 0默认为开发环境,其他为正式环境(1,或者不配)public boolean isNeedPassword(){String version=PropertiesUtil.getValue("system.version");if("0".equals(version))return false;elsereturn true;}
}

3.4 登录与注销代码

package com.cnpc.framework.base.controller;import com.cnpc.framework.base.pojo.ResultCode;
import com.cnpc.framework.base.service.RoleService;
import com.cnpc.framework.base.service.UserService;
import com.cnpc.framework.utils.EncryptUtil;
import com.cnpc.framework.utils.PropertiesUtil;
import com.cnpc.framework.utils.StrUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.Md5CredentialsMatcher;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Set;@Controller
public class LoginController {private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);@Resourceprivate RoleService roleService;private final static String MAIN_PAGE = PropertiesUtil.getValue("page.main");private final static String LOGIN_PAGE = PropertiesUtil.getValue("page.login");@RequestMapping(value = "/login")private String doLogin(HttpServletRequest request, Model model) {//已经登录过,直接进入主页Subject subject = SecurityUtils.getSubject();if (subject != null && subject.isAuthenticated()) {boolean isAuthorized = Boolean.valueOf(subject.getSession().getAttribute("isAuthorized").toString());if (isAuthorized)return MAIN_PAGE;}String userName = request.getParameter("userName");//默认首页,第一次进来if (StrUtil.isEmpty(userName)) {return LOGIN_PAGE;}String password = request.getParameter("password");//密码加密+加盐password = EncryptUtil.getPassword(password, userName);UsernamePasswordToken token = new UsernamePasswordToken(userName, password);token.setRememberMe(true);subject = SecurityUtils.getSubject();String msg;try {subject.login(token);//通过认证if (subject.isAuthenticated()) {Set<String> roles = roleService.getRoleCodeSet(userName);if (!roles.isEmpty()) {subject.getSession().setAttribute("isAuthorized", true);return MAIN_PAGE;} else {//没有授权msg = "您没有得到相应的授权!";model.addAttribute("message", new ResultCode("1", msg));subject.getSession().setAttribute("isAuthorized", false);LOGGER.error(msg);return LOGIN_PAGE;}} else {return LOGIN_PAGE;}//0 未授权 1 账号问题 2 密码错误  3 账号密码错误} catch (IncorrectCredentialsException e) {msg = "登录密码错误. Password for account " + token.getPrincipal() + " was incorrect";model.addAttribute("message", new ResultCode("2", msg));LOGGER.error(msg);} catch (ExcessiveAttemptsException e) {msg = "登录失败次数过多";model.addAttribute("message", new ResultCode("3", msg));LOGGER.error(msg);} catch (LockedAccountException e) {msg = "帐号已被锁定. The account for username " + token.getPrincipal() + " was locked.";model.addAttribute("message", new ResultCode("1", msg));LOGGER.error(msg);} catch (DisabledAccountException e) {msg = "帐号已被禁用. The account for username " + token.getPrincipal() + " was disabled.";model.addAttribute("message", new ResultCode("1", msg));LOGGER.error(msg);} catch (ExpiredCredentialsException e) {msg = "帐号已过期. the account for username " + token.getPrincipal() + "  was expired.";model.addAttribute("message", new ResultCode("1", msg));LOGGER.error(msg);} catch (UnknownAccountException e) {msg = "帐号不存在. There is no user with username of " + token.getPrincipal();model.addAttribute("message", new ResultCode("1", msg));LOGGER.error(msg);} catch (UnauthorizedException e) {msg = "您没有得到相应的授权!" + e.getMessage();model.addAttribute("message", new ResultCode("1", msg));LOGGER.error(msg);}return LOGIN_PAGE;}@RequestMapping(value = "/logout")private String doLogout() {Subject subject = SecurityUtils.getSubject();subject.logout();return LOGIN_PAGE;}}

3.5 前台登录界面代码 login.html核心脚本

  $(function () {$('input').iCheck({checkboxClass: 'icheckbox_square-blue',radioClass: 'iradio_square-blue',increaseArea: '20%' // optional});fillbackLoginForm();$("#login-form").bootstrapValidator({message:'请输入用户名/密码',submitHandler:function (valiadtor,loginForm,submitButton) {rememberMe($("input[name='rememberMe']").is(":checked"));valiadtor.defaultSubmit();},fields:{userName:{validators:{notEmpty:{message:'登录邮箱名或用户名不能为空'}}},password:{validators:{notEmpty:{message:'密码不能为空'}}}}});<#if message??>new LoginValidator({code:"${message.code?default('-1')}",message:"${message.message?default('')}",userName:'userName',password:'password'})</#if>});//使用本地缓存记住用户名密码function rememberMe(rm_flag){//remember meif(rm_flag){localStorage.userName=$("input[name='userName']").val();localStorage.password=$("input[name='password']").val();localStorage.rememberMe=1;}//delete remember msgelse{localStorage.userName=null;localStorage.password=null;localStorage.rememberMe=0;}}//记住回填function fillbackLoginForm(){if(localStorage.rememberMe&&localStorage.rememberMe=="1"){$("input[name='userName']").val(localStorage.userName);$("input[name='password']").val(localStorage.password);$("input[name='rememberMe']").iCheck('check');$("input[name='rememberMe']").iCheck('update');}}

以上的代码是集成shiro认证和鉴权的功能,可能还有些相关的代码,比如密码加密(加盐)没有放上来,不过所有的代码已经放到Github上,大家可以上我的文章头部的Github上来获取所有的代码,让我们开始用Shiro来管理我们的系统安全吧。


http://www.ppmy.cn/news/245869.html

相关文章

1149 Dangerous Goods Packaging(23行代码+详细注释)

分数 25 全屏浏览题目 切换布局 作者 CHEN, Yue 单位 浙江大学 When shipping goods with containers, we have to be careful not to pack some incompatible goods into the same container, or we might get ourselves in serious trouble. For example, oxidizing age…

Aurora自动驾驶安全案例框架

/ 导读 / 自动驾驶公司Aurora于2021年8月推出了有史以来第一个适用于自动驾驶卡车和乘用车的安全案例框架&#xff08;Safety Case Framework)初始版本&#xff0c;解决了自动驾驶卡车和乘用车的安全问题。这使Aurora成为目前业内唯一一家公开分享其安全案例框架的自动驾驶公司…

Android Orm框架(GreenDao)

GreenDao与Ormlite对比 Ormlite&#xff1a;简单好用&#xff0c;比较符合JavaEE开发者使用习惯&#xff0c;注解很方便&#xff1b; GreenDao&#xff1a;为Android大大优化 &#xff0c;最小的内存使用 &#xff0c;非常高的性能优势。 官网地址&#xff1a;http://greend…

Bee,一个ORM框架

Bee&#xff0c;一个ORM框架。一个十分钟即可学会用的框架&#xff0c;一个少编码量的ORM框架&#xff0c; 编码复杂度为O(1)&#xff0c;即用了Bee&#xff0c;你可以不用另外写dao代码。 Bee已对外开源&#xff0c;开源地址&#xff1a; https://github.com/automvc/bee …

SSM 单体框架 - 前端开发:用户和权限模块

用户管理 分页 & 条件查询用户数据 查询条件 1. 用户手机号 2. 注册时间,包含开始日期和结束日期日期选择器组件 在查询条件中使用了 Element UI 中的日期选择器&#xff1a;https://element.eleme.cn/#/zh-CN/component/date-picker#mo-ren-xian-shi-ri-qi 在测试项目…

ORM: 如何管理品牌的在线声誉?

在线名誉管理&#xff08; OnlineReputationManagement&#xff09;又名在线声誉管理 &#xff0c;简称为ORM&#xff0c;是针对线上展示信息进行优化和管理的行为手段。小马识途营销顾问认为在线声誉管理&#xff08;ORM&#xff09;结合了营销、公关和客户服务三种方式&#…

ORM

1. 什么是元类 在Python中一切皆对象&#xff0c;类也是一个对象&#xff0c;实例对象由类创建出来的&#xff0c;类是由元类创建出来的。简而言之&#xff0c;用来创建类的类就叫元类&#xff08;metaclass&#xff09;。 函数type其实就是一个元类&#xff0c;type就是Pytho…

DDD领域驱动

Domain Driven Design DDD&#xff0c;2003年出的书&#xff08;Eric Evans&#xff09;。微服务&#xff0c;2013年。 八叉说DDD&#xff1a;为什么说《领域驱动设计》已经过时了 评论&#xff1a; 他站的基础是互联网企业。但是DDD建模更为重要的还是针对行业软件领域。DDD…