本篇为实战系列第四篇,利用shiro完成登录验证
ssm系列实战(1)——需求分析及数据库设计
ssm系列实战(2)——SSM+Shiro框架整合
ssm系列实战(3)——分类分页的实现
ssm系列实战(4)——利用shiro完成登录验证
ssm系列实战(5)——实现购物车功能,事务实现提交订单
ssm系列源码——GitHub源码地址
Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。Shiro首要的和最重要的目标就是容易使用并且容易理解。
Shiro架构
从大的角度来看,Shiro有三个主要的概念:Subject,SecurityManager,Realms,下面这幅图可以看到这些原件之间的交互。
Subject:翻译为主角,当前参与应用安全部分的主角。可以是用户,可以试第三方服务,可以是cron 任务,或者任何东西。主要指一个正在与当前软件交互的东西。
所有Subject都需要SecurityManager,当你与Subject进行交互,这些交互行为实际上被转换为与SecurityManager的交互
SecurityManager:安全管理员,Shiro架构的核心,它就像Shiro内部所有原件的保护伞。然而一旦配置了SecurityManager,SecurityManager就用到的比较少,开发者大部分时间都花在Subject上面。
请记得,当你与Subject进行交互的时候,实际上是SecurityManager在背后帮你举起Subject来做一些安全操作。
Realms:Realms作为Shiro和你的应用的连接桥,当需要与安全数据交互的时候,像用户账户,或者访问控制,Shiro就从一个或多个Realms中查找。
Shiro提供了一些可以直接使用的Realms,如果默认的Realms不能满足你的需求,你也可以定制自己的Realms
更细节的架构
shiro中默认的过滤器:
这里我们使用shiro来保证用户进入确认订单的方法时一定要是已经登录过的,如果没有登录过将由shiro将用户自动跳转至登录页面。
首先需要在pom.xml中添加相应的shiro的依赖
<!-- shiro-start -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.4</version>
</dependency>
<!-- shiro-end -->
然后web.xml中加入其配置文件和过滤器,记住过滤器要加在配置文件之后,否则会报加载文件的错误
<!-- Spring配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/spring-dao.xml,
classpath:spring/spring-service.xml
classpath:spring/spring-shiro.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- shiro 过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<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>
以下就是我配置的shiro的配置文件,其对应的功能都有添加注释
常用过滤器:
anon:例子/admins/**=anon表示可以匿名访问
authc:例如/admins/user/**=authc表示需要认证才能使用,没有参数
perms:例子/page_base_staff.action = perms["staff"],当前用户需要有staff权限才可以访问。
roles:例子/admins/user/**=roles[admin],当前用户是否有这个角色权限。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- shiro配置 -->
<!-- 自定义Realm -->
<bean id="myRealm" class="cn.zou.shopping.realm.UserRealm" />
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm" />
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!--启用shiro注解 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- Shiro过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/user/loginPage" /> <!--没有登录的时候,跳转到这个页面-->
<property name="unauthorizedUrl" value="/user/nopermission" /> <!--当没有权限的时候,跳转到这个url-->
<property name="filterChainDefinitions">
<value>
/backend/user/getadmin=authc,roles["superadmin"]
/order/**=authc
/backend/**=authc,roles["admin"]
/** = anon
</value>
</property>
</bean>
</beans>
在shiro的配置文件中我们有自定义realm,realm用来验证我们是否登录或有权限。 cn.zou.shopping.realm.UserRealm:
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserServiceImpl userServiceImpl;
/**
* 登录之后用于授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(new HashSet<>(userServiceImpl
.getRoles(username)));
return authorizationInfo;
}
/**
* 用于验证身份
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String)authenticationToken.getPrincipal();
User user = userServiceImpl.selectByUsername(username);
if(user == null||user.getStatus()==2 ) {
//找不到账号或已被冻结
throw new UnknownAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), //用户名
user.getPassword(),
getName() //realm name
);
return authenticationInfo;
}
}
这样,配置基本完成,我们只需要在需要调用验证或者是授权的地方添加验证的代码就可以了,比如我们这里在UserController中用shiro提供的Realm拦截器来判断当前用户是否存在并授权
@RequestMapping("/realm")
public String test(String username, String password, Model model, HttpSession session) {
//获取当前用户对象
Subject subject = SecurityUtils.getSubject();
//生成令牌(传入用户输入的账号和密码)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//认证登录
try {
//这里会加载自定义的realm
//把令牌放到login里面进行查询,如果查询账号和密码时候匹配,如果匹配就把user对象获取出来,失败就抛异常
SecurityUtils.getSubject().login(token);
} catch (UnknownAccountException e) {
//认证登录失败抛出异常
model.addAttribute("message", "账号或密码错误或该账号已被管理员禁用");
return "login";
} catch (IncorrectCredentialsException ice) {
//认证登录失败抛出异常
model.addAttribute("message", "账号或密码错误");
return "login";
}
User loginUser = userService.selectByUsername(username);
session.setAttribute("user", loginUser);
return "redirect:/book/toindex";
}
我们也可以使用Shiro标签库做到在前端页面让用户只能看到他所具有的权限能看到的,而他不具备权限则有些东西看不到
在使用Shiro标签库前,首先需要在JSP引入shiro标签: <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
guest标签 :验证当前用户是否为“访客”,即未认证(包含未记住)的用户
<shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today! </shiro:guest>
- user标签 :认证通过或已记住的用户
-
<shiro:user> Welcome back John! Not John? Click <a href="login.jsp">here<a> to login. </shiro:user>
- authenticated标签 :已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在
-
<shiro:authenticated> <a href="updateAccount.jsp">Update your contact information</a>. </shiro:authenticated>
- notAuthenticated标签 :未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户
-
<shiro:notAuthenticated> Please <a href="login.jsp">login</a> in order to update your credit card information. </shiro:notAuthenticated>
- principal 标签 :输出当前用户信息,通常为登录帐号信息
-
Hello, <shiro:principal/>, how are you today?
- hasRole标签 :验证当前用户是否属于该角色
-
<shiro:hasRole name="administrator"> <a href="admin.jsp">Administer the system</a> </shiro:hasRole>
- lacksRole标签 :与hasRole标签逻辑相反,当用户不属于该角色时验证通过
-
<shiro:lacksRole name="administrator"> Sorry, you are not allowed to administer the system. </shiro:lacksRole>
- hasAnyRole标签 :验证当前用户是否属于以下任意一个角色
-
<shiro:hasAnyRoles name="developer, project manager, administrator"> You are either a developer, project manager, or administrator. </shiro:hasAnyRoles>
- hasPermission标签 :验证当前用户是否拥有指定权限
-
<shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:hasPermission>
- lacksPermission标签 :与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过
-
<shiro:lacksPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:lacksPermission>
我们这个项目在nav.jsp这样用:
让未登录的用户能够看到登录和注册功能,让登录的用户有我的订单,我的购物车,用户头像名字显示等功能。<shiro:notAuthenticated> <a type="button" class="btn btn-info" href="<%=request.getContextPath()%>/user/loginPage">登录</a> <a type="button" class="btn btn-info" href="<%=request.getContextPath()%>/user/registerPage">注册</a> </shiro:notAuthenticated> <shiro:authenticated> <a type="button" class="btn btn-info" href="<%=request.getContextPath()%>/order/myOrder/${user.uId}">我的订单</a> <a type="button" class="btn btn-info" href="<%=request.getContextPath()%>/cart/cart">我的购物车</a> </shiro:authenticated> <!-- User Account: --> <shiro:authenticated> <li class="dropdown user user-menu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <img src="<%=request.getContextPath()%>/img/2.jpg" class="user-image" alt="User Image"> <span class="hidden-xs">${user.nickname}</span> </a> <ul class="dropdown-menu"> <!-- User image --> <li class="user-header"> <img id="user-header" src="<%=request.getContextPath()%>/img/2.jpg" class="img-circle" alt="User Image"> <p>${user.nickname}</p> </li> <!--Menu Body--> <li class="user-body"> <div class="col-xs-4 text-center"> <a href="#">Followers</a> </div> <div class="col-xs-4 text-center"> <a href="#">Sales</a> </div> <div class="col-xs-4 text-center"> <a href="#">Friends</a> </div> </li> <!-- Menu Footer--> <li class="user-footer"> <div class="pull-left"> <a id="systemsettingBtn" href="javascript:void(0)" class="btn btn-default btn-flat">修改密码</a> </div> <div class="pull-right"> <a href="/user/logOut" class="btn btn-default btn-flat">注销</a> </div> </li> </ul> </li> </shiro:authenticated>
有管理员权限的人可以看到普通用户管理,书籍管理功能,有超级管理员权限的人可以看到管理员管理功能
<shiro:hasAnyRoles name="admin,superadmin"> <li> <a href="<%=request.getContextPath()%>/backend/user/getAllUser"> <i class="fa fa-th"></i> <span>普通用户管理</span> </a> </li> <li> <a href="<%=request.getContextPath()%>/backend/book/getAllBook"> <i class="fa fa-dashboard"></i> <span>书籍管理</span> </a> </li> </shiro:hasAnyRoles> <shiro:hasRole name="superadmin"> <li> <a href="<%=request.getContextPath()%>/backend/user/getadmin"> <i class="fa fa-th"></i> <span>管理员管理</span> </a> </li> </shiro:hasRole>