今天在學習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
