shiro登录认证 与授权
shiro配置文件
<!-- 以下都是Apache安全框架Shiro的配置 --> <!-- 数据库保存的密码是使用MD5算法加密的,所以这里需要配置一个密码匹配对象 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean> <!-- 缓存管理 ,第一次 会去数据库中取权限,后面就从缓存中取 --> <bean id="shiroCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> <!-- 自定义realm --> <bean id="customRealm" class="com.ecenter.sso.security.CustomRealm"> <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 --> <property name="credentialsMatcher" ref="credentialsMatcher" /> </bean> <!-- Shiro安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <property name="cacheManager" ref="shiroCacheManager" /> <property name="sessionManager" ref="sessionManager"/> </bean> <aop:config proxy-target-class="true"></aop:config> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--Shiro的核心安全接口,这个属性是必须的--> <property name="securityManager" ref="securityManager"></property> <!--要求登录时的链接(登录页面地址),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面--> <property name="loginUrl" value="/login"></property> <!--登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码)--> <!--<property name="successUrl" value="/"></property>--> <!--用户访问未对其授权的资源时,所显示的连接--> <property name="unauthorizedUrl" value="/noPower.jsp"></property> <property name="filterChainDefinitions"> <value> /checkCode/getCodeImage=anon /login=anon /userInfo/edit=anon /userInfo/password=anon /userInfo/passwordChange=anon /doLogin=anon /lib/**=anon /**=authc </value> </property> </bean>
前端登录请求
$.ajax({ type : "POST", url : "/doLogin", data : $('#form').serialize(), error : function(request) { alert("操作失败!"); }, success : function(data) { if (data == '200') { if ($('#remember-password').attr('checked')) { var cookie = new rememberPassword(); cookie.cookieRemeber(); } window.location.href = "/index"; } else if (data == '500') { $('#password').val(''); $('#checkCode').val(''); $('#checkCode').attr('placeholder',''); changeImg(); $('#password').attr('placeholder','用户名或密码不正确'); $('#password').focus(); } else if (data == '401') { $('#checkCode').val(''); $('#checkCode').attr('placeholder','验证码不正确'); changeImg(); }else if(data == '501'){ layui.use(['laypage', 'layer'], function(){ var laypage = layui.laypage ,layer = layui.layer; layer.open({ type: 2, //类型,解析url closeBtn: 1, //关闭按钮是否显示 1显示0不显示 title: '密码修改', //页面标题 shadeClose: true, //点击遮罩区域是否关闭页面 shade: 0.3, //遮罩透明度 area: ['650px', '300px'], //弹出层页面比例 content: '/userInfo/password' //弹出层的url }); }); }else { if(data == '10'){ $('#username').val(''); $('#checkCode').val(''); $('#password').val(''); $('#username').attr('placeholder','用户已删除,无法登陆'); }else if(data == '11'){ $('#username').val(''); $('#checkCode').val(''); $('#password').val(''); $('#username').attr('placeholder','用户暂未通过审核'); }else if(data == '12'){ $('#username').val(''); $('#checkCode').val(''); $('#password').val(''); $('#username').attr('placeholder','用户未能通过审核'); }else if(data == '14'){ $('#username').val(''); $('#checkCode').val(''); $('#password').val(''); $('#username').attr('placeholder','用户已被禁用'); }else if(data == '3'){ $('#username').val(''); $('#checkCode').val(''); $('#password').val(''); $('#username').attr('placeholder','用户已被禁用'); } } } });
后端登录接口 /dologin
@RequestMapping("/doLogin") @ResponseBody public String doLogin(HttpServletRequest request, HttpServletResponse response, @RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("checkCode") String checkCode) { if (!checkCode.equalsIgnoreCase((String) request.getSession().getAttribute("code"))) { return Constant.RES_CHECKCODE_ERROR; // 验证码不正确 } UsernamePasswordToken token = new UsernamePasswordToken(username,password, true); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); List<Session> loginedList = getLoginedSession(subject); for (Session session : loginedList) { session.stop(); } Session session = subject.getSession(); if (subject.isAuthenticated()) { Map<String, Object> claims = new HashMap<String, Object>(); User user = iUserService.findByUsername(username); claims.put("username", username); claims.put("usertype", user.getUsertype()); claims.put("areaCode", user.getAreacode()); if("123456".equals(password)){ session.setAttribute("username", username); return Constant.PASSWORD_EASY;//密码太简单 } // 验证成功后 使用JWT生成加密的token String JWT = JavaWebToken.createJavaWebToken(claims); session.setAttribute("Authorization", JWT); session.setAttribute("areaCode", user.getAreacode()); session.setAttribute("usersession", user); return Constant.RES_SUCCESS; } else { return Constant.RES_FAIL; } } catch (IncorrectCredentialsException e) {// 登录密码错误 return Constant.RES_FAIL; } catch (ExcessiveAttemptsException e) { // 登录失败次数过多 return Constant.RES_FAIL; } catch (UnknownAccountException e) { // 帐号不存在 return Constant.RES_FAIL; } }
subject.login(token);源码追踪

自定义的 Realm 对象
package com.ecenter.sso.security; public class CustomRealm extends AuthorizingRealm { @Autowired private IUserService iUserService; @Autowired(required=true) private SessionDAO sessionDAO; //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // token中包含用户输入的用户名和密码 // 第一步从token中取出用户名 String userName = (String) token.getPrincipal(); // 第二步:根据用户输入的userCode从数据库查询 User user = iUserService.findByUsername((String) token.getPrincipal()); // 如果查询不到返回null if (user == null) {// return null; } // 获取数据库中的密码 String password = user.getPassword(); //认证的用户,正确的密码 AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(userName, password, this.getName()); Session currentSession = null; Collection<Session> sessions = sessionDAO.getActiveSessions(); /* for(Session session:sessions){ if(userName.equals(String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)))) { //session.setTimeout(0);//设置session立即失效,即将其踢出系统 session.stop(); break; } }*/ return authcInfo; } /** * 授权,只有成功通过<span style="font-family: Arial, Helvetica, sans-serif;"> * doGetAuthenticationInfo方法的认证后才会执行。</span> */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 从 principals获取主身份信息 // 将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型), String username = (String) principals.getPrimaryPrincipal(); // 根据身份信息获取权限信息 // 从数据库获取到权限数据 // 单独定一个集合对象 ArrayList<String> permissions = (ArrayList<String>) iUserService.getUserPermission(username); // 查到权限数据,返回授权信息(要包括 上边的permissions) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // 将上边查询到授权信息填充到simpleAuthorizationInfo对象中 simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; } // 清除缓存 public void clearCached() { PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principals); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?