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