基于权限安全框架Shiro的登录验证功能实现

1,147
原文链接: www.cnblogs.com

目前在企业级项目里做权限安全方面喜欢使用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会话过期,需要重新登录,保证系统安全性
这里写图片描述

本博客只提供基于Shiro的登录验证实现,具体代码可以去我的github下载:github.com/u014427391/…
欢迎star

IT程序员