基於許可權安全框架Shiro的登入驗證功能實現
阿新 • • 發佈:2019-02-18
目前在企業級專案裡做許可權安全方面喜歡使用Apache開源的Shiro框架或者Spring框架的子框架Spring Security。
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼學和會話管理。
Shiro框架具有輕便,開源的優點,所以本部落格介紹基於Shiro的登入驗證實現。
在maven里加入shiro需要的jar
<!--shiro start-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId >shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<!-- shiro end-->
在web.xml加上Shiro過濾器配置:
<!-- Shiro過濾器配置 start -->
<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>
<!-- Shiro過濾器配置 end -->
編寫shiro的ShiroRealm類:
package org.muses.jeeplatform.core.security.shiro;
import javax.annotation.Resource;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.muses.jeeplatform.model.entity.User;
import org.muses.jeeplatform.service.UserService;
/**
* @description 基於Shiro框架的許可權安全認證和授權
* @author Nicky
* @date 2017年3月12日
*/
public class ShiroRealm extends AuthorizingRealm {
/**註解引入業務類**/
@Resource
UserService userService;
/**
* 登入資訊和使用者驗證資訊驗證(non-Javadoc)
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到使用者名稱
String password = new String((char[])token.getCredentials()); //得到密碼
User user = userService.findByUsername(username);
/**檢測是否有此使用者 **/
if(user == null){
throw new UnknownAccountException();//沒有找到賬號異常
}
/**檢驗賬號是否被鎖定 **/
if(Boolean.TRUE.equals(user.getLocked())){
throw new LockedAccountException();//丟擲賬號鎖定異常
}
/**AuthenticatingRealm使用CredentialsMatcher進行密碼匹配**/
if(null != username && null != password){
return new SimpleAuthenticationInfo(username, password, getName());
}else{
return null;
}
}
/**
* 授權查詢回撥函式, 進行鑑權但快取中無使用者的授權資訊時呼叫,負責在應用程式中決定使用者的訪問控制的方法(non-Javadoc)
* @see AuthorizingRealm#doGetAuthorizationInfo(PrincipalCollection)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
String username = (String)pc.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.getRoles(username));
authorizationInfo.setStringPermissions(userService.getPermissions(username));
System.out.println("Shiro授權");
return authorizationInfo;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
}
在Spring框架裡整合Shiro,加入配置
<!-- Shiro start -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="ShiroRealm" />
</bean>
<!-- 專案自定義的Realm -->
<bean id="ShiroRealm" class="org.muses.jeeplatform.core.security.shiro.ShiroRealm" ></bean>
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/admin/index" />
<property name="unauthorizedUrl" value="/login" />
<property name="filterChainDefinitions">
<value>
/static/** = anon
/upload/** = anon
/plugins/** = anon
/code = anon
/login = anon
/logincheck = anon
/** = authc
</value>
</property>
</bean>
<!-- Shiro end -->
登入驗證控制類實現:
package org.muses.jeeplatform.web.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.muses.jeeplatform.core.Constants;
import org.muses.jeeplatform.model.entity.Menu;
import org.muses.jeeplatform.model.entity.Permission;
import org.muses.jeeplatform.model.entity.Role;
import org.muses.jeeplatform.model.entity.User;
import org.muses.jeeplatform.service.MenuService;
import org.muses.jeeplatform.service.UserService;
import org.muses.jeeplatform.utils.Tools;
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.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* @description 登入操作的控制類,使用Shiro框架,做好了登入的許可權安全認證,
* getRemortIP()方法獲取使用者登入時的ip並儲存到資料庫
* @author Nicky
* @date 2017年3月15日
*/
@Controller
public class LoginController extends BaseController {
@Autowired
UserService userService;
@Autowired
MenuService menuService;
/**
* 獲取登入使用者的IP
* @throws Exception
*/
public void getRemortIP(String username) {
HttpServletRequest request = this.getRequest();
Map<String,String> map = new HashMap<String,String>();
String ip = "";
if (request.getHeader("x-forwarded-for") == null) {
ip = request.getRemoteAddr();
}else{
ip = request.getHeader("x-forwarded-for");
}
map.put("username", username);
map.put("loginIp", ip);
userService.saveIP(map);
}
/**
* 訪問後臺登入頁面
* @return
* @throws Exception
*/
@RequestMapping(value="/login",produces="text/html;charset=UTF-8")
public ModelAndView toLogin()throws ClassNotFoundException{
ModelAndView mv = this.getModelAndView();
mv.setViewName("admin/frame/login");
return mv;
}
/**
* 基於Shiro框架的登入驗證,頁面傳送JSON請求資料,
* 服務端進行登入驗證之後,返回Json響應資料,"success"表示驗證成功
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value="/logincheck", produces="application/json;charset=UTF-8")
@ResponseBody
public String loginCheck(HttpServletRequest request)throws AuthenticationException{
JSONObject obj = new JSONObject();
String errInfo = "";//錯誤資訊
String logindata[] = request.getParameter("LOGINDATA").split(",");
if(logindata != null && logindata.length == 3){
//獲取Shiro管理的Session
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
String codeSession = (String)session.getAttribute(Constants.SESSION_SECURITY_CODE);
String code = logindata[2];
/**檢測頁面驗證碼是否為空,呼叫工具類檢測**/
if(Tools.isEmpty(code)){
errInfo = "nullcode";
}else{
String username = logindata[0];
String password = logindata[1];
if(Tools.isNotEmpty(codeSession) && codeSession.equalsIgnoreCase(code)){
//Shiro框架SHA加密
String passwordsha = new SimpleHash("SHA-1",username,password).toString();
System.out.println(passwordsha);
//檢測使用者名稱和密碼是否正確
User user = userService.doLoginCheck(username,passwordsha);
if(user != null){
if(Boolean.TRUE.equals(user.getLocked())){
errInfo = "locked";
}else{
//Shiro新增會話
session.setAttribute("username", username);
session.setAttribute(Constants.SESSION_USER, user);
//刪除驗證碼Session
session.removeAttribute(Constants.SESSION_SECURITY_CODE);
//儲存登入IP
getRemortIP(username);
/**Shiro加入身份驗證**/
Subject sub = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
sub.login(token);
}
}else{
//賬號或者密碼錯誤
errInfo = "uerror";
}
if(Tools.isEmpty(errInfo)){
errInfo = "success";
}
}else{
//缺少引數
errInfo="codeerror";
}
}
}
obj.put("result", errInfo);
return obj.toString();
}
/**
* 後臺管理系統主頁
* @return
* @throws Exception
*/
@RequestMapping(value="/admin/index")
public ModelAndView toMain() throws AuthenticationException{
ModelAndView mv = this.getModelAndView();
/**獲取Shiro管理的Session**/
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
User user = (User)session.getAttribute(Constants.SESSION_USER);
if(user != null){
...//業務實現
}else{
//會話失效,返回登入介面
mv.setViewName("admin/frame/login");
}
mv.setViewName("admin/frame/index");
return mv;
}
/**
* 登出登入
* @return
*/
@RequestMapping(value="/logout")
public ModelAndView logout(){
ModelAndView mv = this.getModelAndView();
/**Shiro管理Session**/
Subject sub = SecurityUtils.getSubject();
Session session = sub.getSession();
session.removeAttribute(Constants.SESSION_USER);
session.removeAttribute(Constants.SESSION_SECURITY_CODE);
/**Shiro銷燬登入**/
Subject subject = SecurityUtils.getSubject();
subject.logout();
/**返回後臺系統登入介面**/
mv.setViewName("admin/frame/login");
return mv;
}
}
前端Ajax和JQeury校驗實現:
/**客戶端校驗**/
function checkValidity() {
if ($("#username").val() == "") {
$("#username").tips({
side : 2,
msg : '使用者名稱不得為空',
bg : '#AE81FF',
time : 3
});
$("#username").focus();
return false;
}
if ($("#password").val() == "") {
$("#password").tips({
side : 2,
msg : '密碼不得為空',
bg : '#AE81FF',
time : 3
});
$("#password").focus();
return false;
}
if ($("#code").val() == "") {
$("#code").tips({
side : 1,
msg : '驗證碼不得為空',
bg : '#AE81FF',
time : 3
});
$("#code").focus();
return false;
}
return true;
}
/**伺服器校驗**/
function loginCheck(){
if(checkValidity()){
var username = $("#username").val();
var password = $("#password").val();
var code = username+","+password+","+$("#code").val();
$.ajax({
type: "POST",//請求方式為POST
url: 'logincheck',//檢驗url
data: {LOGINDATA:code,tm:new Date().getTime()},//請求資料
dataType:'json',//資料型別為JSON型別
cache: false,//關閉快取
success: function(data){//響應成功
if("success" == data.result){
$("#login").tips({
side : 1,
msg : '正在登入 , 請稍後 ...',
bg : '#68B500',
time : 10
});
window.location.href="admin/index";
}else if("uerror" == data.result){
$("#username").tips({
side : 1,
msg : "使用者名稱或密碼有誤",
bg : '#FF5080',
time : 15
});
$("#username").focus();
}else if("codeerror" == data.result){
$("#code").tips({
side : 1,
msg : "驗證碼輸入有誤",
bg : '#FF5080',
time : 15
});
$("#code").focus();
}else if("locked" == data.result){
alert('您的賬號被鎖定了,嗚嗚');
}else{
$("#username").tips({
side : 1,
msg : "缺少引數",
bg : '#FF5080',
time : 15
});
$("#username").focus();
}
}
});
}
}
登入成功,Session會話過期,需要重新登入,保證系統安全性