Shiro认证的另一种方式
今天在学习shiro的时候使用另一种shiro验证的方式。
总体的思路是:
(1)先在自己的方法中进行身份的验证以及给出提示信息。(前提是将自己的验证方法设为匿名可访问)
(2)当验证成功之后到Shiro中认证以及授权一下即可(授权的时候保存用户的权限与角色字符串)。当然,在自己验证成功之后需要向session中存入所需要的数据。
(3)验证成功之后向前台返回登录成功,此时也已经在shiro中授权,后台可以请求后台地址。如果后台地址不确定的时候需要在验证成功向前台反馈的时候携带一个成功之后的地址。
其大致流程如下:
(1)配置静态资源可以匿名访问
(2)shiro配置loginUrl认证提交地址,login.do。(此方法只是简单的将页面的地址转发到login.jsp)
(3)登录的地址: userLogin.do 处理登录流程的controller
(1)正常登录
(2)登录成功之后进行shiro权限认证
(3)登录失败提示失败信息
Service登录获取登录信息,该用户所有的权限信息,该用户所有的权限码,返回到Controller
controller根据登录信息判断是否进行shiro认证。
如果登录信息是登录成功:
将当前用户对象存到session中,将当前用户的菜单存到session中
前台根据返回的信息判断是否进行页面跳转(登录成功跳转到index.jsp)
如果登录失败:啥都不做
0.数据库中权限表结构:
menu1表示一级菜单,menu2表示2级菜单,menu3表示3级菜单。
permissionCode是权限码,在代码或者jsp中可以进行判断。
代码中验证:
// 获取用户信息 Subject currentUser = SecurityUtils.getSubject(); boolean permitted = currentUser.isPermitted("exammanager:factory");// 判断是否有全厂管理的权限,有就不添加部门ID,没有就设为当前Session中的部门ID String departmentId = permitted ? null : departmentIdSession;
JSP页面进行验证权限:
<shiro:hasPermission name="department:operating"> <script> hasOperatingDepart = true; </script> </shiro:hasPermission>
1.Shiro配置
主要是配置一下登录的请求地址:这是一个虚拟的地址,仅仅是为了将首页地址隐藏,
然后配置一些可以匿名访问的资源,就是不需要登录就可以访问的资源。 ** 两颗星表示任意目录均可以访问,* 表示当前目录的资源可以访问。一般都是两颗 * 。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> <!-- 记住我 --> <property name="rememberMeManager" ref="rememberMeManager"/> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入session管理器 --> <property name="sessionManager" ref="sessionManager"/> </bean> <!-- 自定义Realm进行认证和授权 --> <bean id="myRealm" class="cn.xm.jwxt.shiro.CustomRealm" /> <!-- 自定义form认证过虑器 --> <!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 --> <bean id="formAuthenticationFilter" class="cn.xm.jwxt.shiro.CustomFormAuthenticationFilter"> <property name="usernameParam" value="usercode" /> <property name="passwordParam" value="password" /> <property name="usersort" value="usersort" /> <!--<property name="rememberMeParam" value="rememberMe"/>--> </bean> <!-- ehcache缓存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/> </bean> <!-- session会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- session失效时间 单位毫秒 --> <property name="globalSessionTimeout" value="18000000"/> <!-- 删除失效的session --> <property name="deleteInvalidSessions" value="true"/> </bean> <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie" /> </bean> <!-- 会话Cookie模板 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="-1"/> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe"/> <property name="httpOnly" value="true"/> <property name="maxAge" value="2592000"/><!-- 30天 --> </bean> <!-- logout --> <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <!-- <property name="redirectUrl" value="/index.jsp" /> --> <property name="redirectUrl" value="/index.jsp" /> </bean> <!-- web.xml中shiro的filter对应的bean --> <!-- Shiro 的Web过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 --> <property name="loginUrl" value="/login.do" /> <!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 --> <property name="successUrl" value="/index.jsp" /> <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 --> <property name="unauthorizedUrl" value="/refuse.jsp" /> <!-- 自定义filter配置 --> <property name="filters"> <map> <!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中 --> <entry key="authc" value-ref="formAuthenticationFilter" /> </map> </property> <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --> <property name="filterChainDefinitions"> <value> <!-- 静态资源放行 --> /css/** = anon /fonts/** = anon /images/** = anon /js/** = anon /lib/** = anon /login.jsp=anon /userLogin.do=anon <!-- 请求 logout.action地址,shiro去清除session --> /logout.do = logout <!-- /** = authc 所有url都必须认证通过才可以访问 --> /** = authc </value> </property> </bean> <!-- 开启Shiro注解 --> <aop:config proxy-target-class="true"></aop:config> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> </beans>
2.User.java (将来要被保存到session中的实体类)
包含当前用户的身份信息想,以及当前用户的菜单以及所有的权限。
package cn.xm.jwxt.bean.system; import java.util.List; import java.util.Set; public class User { private String userid;//用户ID private String usercode;//用户code(登录用这个) private String username;//用户姓名 private String password;//用户密码 private String usersort;//用户类型 private String userstuteanum;//用户编号(学号或者教师编号) private String userunitname;//用户所属单位名称(班级名称或者教研室名称) private String userunitnum;//班级编码或者教研室编码 private String isuse;//是否在用 private String remark1;//备注 private List<Permission> permissions;//所有权限 private Set<String> permissionCodes;//所有权限码 private List<Permission> menuPermissions;//所有菜单权限(按序号排列)
.....
3.Controller层 login 方法:
第一个仅仅是转发页面,第二个是真正的处理登录的业务逻辑,登录成功之后再到shiro认证一下即可完成shiro登录。
package cn.xm.jwxt.controller.system; import cn.xm.jwxt.bean.system.User; import cn.xm.jwxt.service.system.UserService; import org.apache.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.sql.SQLException; import java.util.Map; /** * @Author: qlq * @Description 登录controller * @Date: 11:28 2018/5/13 */ @Controller public class LoginController { private final String SESSION_USER_INFO="userinfo"; //用户信息的session名 private final String SESSION_PERMISSION_INFO="permissioninfo"; //权限信息的session名 @Autowired private UserService userService;//用于处理登录的业务流程 private Logger logger = Logger.getLogger(LoginController.class); // 未登录的用户默认重定向到的地址.和applicationContext-shiro.xml中配置的loginurl一致(只是简单的将地址转发到login.jsp) @RequestMapping("/login") public String login() throws Exception { return "login"; } /** * @param usercode 账户 * @param password 密码 * @param usersort 用户类型 * @return 登录结果 * @throws Exception */ @RequestMapping("/userLogin") public @ResponseBody Map<String,Object> userLogin(@RequestParam(defaultValue = "1") String usercode, @RequestParam(defaultValue = "1") String password, @RequestParam(defaultValue = "1") String usersort, HttpServletRequest request) { Map<String, Object> userLoginInfo =null; try { userLoginInfo = userService.getUserLoginInfo(usercode, password, usersort); } catch (SQLException e) { logger.error("登录出错",e); userLoginInfo.put("loginInfo","登录失败!"); return userLoginInfo; } String loginInfo = (String) userLoginInfo.get("loginInfo");//获取登录信息 ////shiro的使用需要先把用户的信息放入session 需要先用shiro的方法进行登录 但是登录方法已经写好。这里在登录成功之后用shiro的方法再登录一次 if("登录成功" == loginInfo){ //1.存session东西 HttpSession session=request.getSession(); User user = (User) userLoginInfo.get("user"); session.setAttribute(SESSION_USER_INFO, user); session.setAttribute(SESSION_PERMISSION_INFO, user.getPermissions()); userLoginInfo.remove("user");//从map中删除用户返回前台 //2.到shiro验证授权 Subject currentUser=SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken(usercode, password); currentUser.login(token); } return userLoginInfo; } }
4.Service层登录方法:
根据用户输入的usercode查询数据库,并依次匹配密码和用户类型
如果登录之后再提取该用户的所有权限与菜单permission。
@Override public Map<String, Object> getUserLoginInfo(String usercode, String password, String usersort) throws SQLException { Map<String,Object> result= new HashMap<String,Object>(); String loginInfo = null;//登录信息 List<Permission> permissions = null;//所有权限 Set<String> permissionCodes = null;//所有权限码 //1.进行登录(分别根据usercode,password,usersort进行判断) UserExample userExample = new UserExample(); UserExample.Criteria criteria = userExample.createCriteria(); criteria.andUsercodeEqualTo(usercode); List<User> users = userMapper.selectByExample(userExample); //1.1判断账户 if(users == null || users.size()==0){ loginInfo = "账户不存在!"; result.put("loginInfo",loginInfo); return result; } User user = users.get(0); //1.2判断密码 String password_sql = user.getPassword(); if(password_sql == null || !password_sql.equals(password)){ loginInfo = "密码错误!"; result.put("loginInfo",loginInfo); return result; } //1.3 判断类型 String usersort_sql = user.getUsersort(); if(usersort_sql == null || !usersort_sql.equals(usersort)){ loginInfo = "账户类型错误!"; result.put("loginInfo",loginInfo); return result; } //1.4判断账户状态 String userStatus_sql = user.getIsuse(); if(userStatus_sql == null || userStatus_sql.equals("0")){ loginInfo = "账户已锁定,请联系管理员开启账户!"; result.put("loginInfo",loginInfo); return result; } //2.查询该用户的所有的权限List<Permission> permissions permissions = this.selectPermissionsByUserId(user.getUserid()); if(permissions == null || permissions.size() == 0){ loginInfo = "该账户还没有任何权限,请联系管理员分配权限!"; result.put("loginInfo",loginInfo); return result; } //3.提取该用户的所有的权限码Set<String> permissionCodes permissionCodes = new HashSet<>(); for(Permission permission:permissions){ if(ValidateCheck.isNotNull(permission.getPermissioncode())){ permissionCodes.add(permission.getPermissioncode()); } } //4.提取该用户所有的菜单: Map condition = new HashMap(); condition.put("userId",user.getUserid()); condition.put("menu","1"); List<Permission> menus = this.getUserPermissionsByCondition(condition); //5.将登录信息设为登录成功 loginInfo = "登录成功"; result.put("loginInfo",loginInfo); user.setPermissions(permissions);//所有权限 user.setPermissionCodes(permissionCodes);//所有权限码 user.setMenuPermissions(menus);//所有菜单权限 result.put("user",user); return result; }
5.Shiro的权限认证和授权:(自定义realm进行认证和授权)
认证:如果认证通过,返回一个 SimpleAuthenticationInfo 对象,认证失败返回 null
授权: 根据 userId 查询该用户的所有权限码(permissionCode) ,以字符串集合的形式添加(可以是set,也可以是list的形式)。以字符串的形式添加角色信息(Set<roleName>的形式)
package cn.xm.jwxt.shiro; import cn.xm.jwxt.bean.system.Permission; import cn.xm.jwxt.bean.system.User; import cn.xm.jwxt.service.system.UserService; import cn.xm.jwxt.utils.ValidateCheck; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; 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.HashSet; import java.util.List; import java.util.Set; /** * @Author: qlq * @Description 自定义realm。根据上面传下来的token去数据库查信息,查到返回一个SimpleAuthenticationInfo,查不到返回null(用于shiro认证) * @Date: 21:56 2018/5/6 */ public class CustomRealm extends AuthorizingRealm { @Autowired private UserService userService; // 设置realm的名称 @Override public void setName(String name) { super.setName("customRealm"); } // realm的认证方法,从数据库查询用户信息 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userCode=(String)token.getPrincipal();//获取token的主身份(登录的username User user = null; try { user = userService.getUserByUserCode(userCode); } catch (Exception e) { e.printStackTrace(); } AuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return authenticationInfo; } // 用于授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //0.下面方法principals.getPrimaryPrincipal()获取的是在上面认证的时候装进AuthenticationInfo的对象 String userId=((User)(principals.getPrimaryPrincipal())).getUserid(); SimpleAuthorizationInfo simpleAuthorizationInfo=null; try { simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //1.设置所有的权限(注意权限是以字符串的形式保存的权限码) List<Permission> permissions1 = userService.selectPermissionsByUserId(userId);//获取所有权限码 Set<String> permissions = new HashSet<>(); for(Permission permission:permissions1){ if(ValidateCheck.isNotNull(permission.getPermissioncode())){ permissions.add(permission.getPermissioncode()); } } if (permissions != null && permissions.size()>0) { simpleAuthorizationInfo.setStringPermissions(permissions); } //2.设置角色,角色也是以字符串的形式表示(这里存的是角色名字) Set<String> userRoleNames = userService.getUserRoleNameByUserId(userId); if(userRoleNames != null && userRoleNames.size()>0){ simpleAuthorizationInfo.setRoles(userRoleNames); } } catch (Exception e) { e.printStackTrace(); } return simpleAuthorizationInfo; } // 清除缓存 public void clearCached() { PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principals); } }
注意: (principals.getPrimaryPrincipal())).getUserid(); 是在认证的时候返回 AuthenticationInfo 对象的第一个参数。
6.前端登录方法:
如果返回的不是登录成功就是登录失败了,就弹出失败信息。否则就是登录成功,登录成功之后访问我后台首页。此时也已经完成shiro认证,可以访问后台。
//1.监听登录表单的提交 form.on('submit(login)', function(data){ $.ajax({ url:contextPath+'/userLogin.do', type:"post", async:false, data:$("#loginForm").serialize(), success:function (response) { if(response != null && response.loginInfo != "登录成功"){ layer.msg(response.loginInfo,{time:2*1000,icon:2,shade: [0.8, '#393D49']}); }else{ window.location.href=contextPath;//登录成功跳转到首页 } }, dataType:'json'}); });
7.前端首页根据session中的用户的菜单显示对应的菜单:
<div class="left-nav"> <div id="side-nav"> <ul id="nav"> <!--遍历一级菜单--> <c:forEach var="menu1" items="${userinfo.menuPermissions}"> <c:if test="${menu1.permissiontype=='menu1' }"> <li> <a href=javascript:void(0)> <cite>${menu1.permissionname}</cite> <i class="iconfont nav_right"></i> </a> <ul class="sub-menu"> <!--遍历二级菜单--> <c:forEach var="menu2" items="${userinfo.menuPermissions}"> <c:if test="${menu2.permissiontype=='menu2' and menu2.parentid == menu1.permissionid}"> <!--如果url不为空就是二级菜单--> <c:if test="${menu2.url != '' && menu2.url != null}"> <li> <a _href="${menu2.url}"> <cite>${menu2.permissionname}</cite> </a> </li> </c:if> <!--如果url为空就是三级菜单--> <c:if test="${menu2.url == null || menu2.url == ''}"> <li> <a href="javascript:;"> <cite>${menu2.permissionname}</cite> <i class="iconfont nav_right"></i> </a> <ul class="sub-menu"> <%--遍历获取三级菜单--%> <c:forEach var="menu3" items="${userinfo.menuPermissions}"> <c:if test="${menu3.permissiontype=='menu3' and menu3.parentid == menu2.permissionid}"> <li> <a _href="${menu3.url}"> <cite>${menu3.permissionname}</cite> </a> </li> </c:if> </c:forEach> </ul> </li> </c:if> </c:if> </c:forEach> </ul> </li> </c:if> </c:forEach> </ul> </div> </div>
8.前端退出系统的操作:
<script> /** * 退出系统相关操作 */ function logoutSystem(){ layui.use(['layer'],function () { var layer = layui.layer; layer.confirm("确认退出系统?",{icon:3,shade: [0.8, '#393D49']},function () { window.location.href=contextPath+"/logout.do"; }) }) } </script>
后端只用在shiro配置文件中配置一下 /logout.do=logout 当访问这个路径的时候shiro会自动清除session中的东西。
<!-- 请求 logout.action地址,shiro去清除session --> /logout.do = logout