基于权限安全框架 Shiro 的登录验证功能实现 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
javahih
V2EX    Java

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

  •  
  •   javahih 2017-12-25 12:00:31 +08:00 4270 次点击
    这是一个创建于 2927 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前在企业级项目里做权限安全方面喜欢使用 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=") 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=") @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 条回复    2017-12-25 15:29:19 +08:00
    ymcisokay
        1
    ymcisokay  
       2017-12-25 13:18:36 +08:00
    最近刚在学 shiro,mark 一下。
    biaoliruyi
        2
    biaoliruyi  
       2017-12-25 14:37:03 +08:00
    mark
    lhx2008
        4
    lhx2008  
       2017-12-25 14:45:49 +08:00 via Android
    用自定义注解和拦截器撸了一个,勉强够用
    lhx2008
        5
    lhx2008  
       2017-12-25 14:51:05 +08:00 via Android
    在 controller 加类注解或 action 者方法注解,拦截器那边把注解读出来,就可以做不同用户组的鉴权了,还可以顺带加上 user 自动注入
    qinxi
        6
    qinxi  
       2017-12-25 15:29:19 +08:00
    我就想说

    代码不忍直视
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5182 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 05:51 PVG 13:51 LAX 21:51 JFK 00:51
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86