javahih
V2EX  ›  Java

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

  •  
  •   javahih · Dec 25, 2017 · 4503 views
    This topic created in 3061 days ago, the information mentioned may be changed or developed.

    目前在企业级项目里做权限安全方面喜欢使用 Apache 开源的 Shiro 框架或者 Spring 框架的子框架 Spring Security。

    Apache Shiro 是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码学和会话管理。

    Shiro 框架具有轻便,开源的优点,所以本博客介绍基于 Shiro 的登录验证实现。

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

    在 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 下载: https://github.com/u014427391/jeeplatform 欢迎 star

    6 replies    2017-12-25 15:29:19 +08:00
    ymcisokay
        1
    ymcisokay  
       Dec 25, 2017
    最近刚在学 shiro,mark 一下。
    biaoliruyi
        2
    biaoliruyi  
       Dec 25, 2017
    mark
    lhx2008
        4
    lhx2008  
       Dec 25, 2017 via Android
    用自定义注解和拦截器撸了一个,勉强够用
    lhx2008
        5
    lhx2008  
       Dec 25, 2017 via Android
    在 controller 加类注解或 action 者方法注解,拦截器那边把注解读出来,就可以做不同用户组的鉴权了,还可以顺带加上 user 自动注入
    qinxi
        6
    qinxi  
       Dec 25, 2017
    我就想说

    代码不忍直视
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5846 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 71ms · UTC 01:57 · PVG 09:57 · LAX 18:57 · JFK 21:57
    ♥ Do have faith in what you're doing.