1. 程式人生 > >基於許可權安全框架Shiro的登入驗證功能實現

基於許可權安全框架Shiro的登入驗證功能實現

目前在企業級專案裡做許可權安全方面喜歡使用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會話過期,需要重新登入,保證系統安全性
這裡寫圖片描述