本系列博文目录:http://www.javashuo.com/article/p-ewndobct-kn.htmljavascript
本文所提供的项目实例,是我将公司项目中的shiro代码进行了抽取、整理并添加了一些注释而造成的。html
因此例子中并不包含shiro全部的功能,可是本系列文章前9篇所讲解的内容在这里都是能够找到的。java
本示例项目所使用的技术以下:linux
集成开发环境为IDEA,项目构建使用spring boot,包管理使用maven,页面展现使用freemaker,控制层使用spring mvc等。git
在本篇博文中会贴出主要代码,完整的项目已经上传到码云你们能够下载查看使用。web
项目码云地址:http://git.oschina.net/imlichao/shiro-exampleajax
package pub.lichao.shiro.config; import com.jagregory.shiro.freemarker.ShiroTags; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; import java.util.HashMap; import java.util.Map; /** * FreeMarker配置文件 */ @Configuration public class FreemarkerConfig { @Bean public FreeMarkerConfigurer freeMarkerConfigurer(FreeMarkerProperties freeMarkerProperties) { FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setTemplateLoaderPaths(freeMarkerProperties.getTemplateLoaderPath()); //模板加载路径默认 "classpath:/templates/" configurer.setDefaultEncoding("utf-8");//设置页面默认编码(不设置页面中文乱码) Map<String,Object> variables=new HashMap<String,Object>(); variables.put("shiro", new ShiroTags()); configurer.setFreemarkerVariables(variables);//添加shiro自定义标签 return configurer; } }
package pub.lichao.shiro.config; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; import pub.lichao.shiro.shiro.AuthenticationFilter; import pub.lichao.shiro.shiro.ShiroRealm; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * Shiro配置 */ @Configuration public class ShiroConfig { /** * 建立EhCache缓存类 * @return */ @Bean(name = "shiroCacheManager") public EhCacheManager shiroCacheManager() { EhCacheManager ehCacheManager = new EhCacheManager(); ehCacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");//指定缓存配置文件路径 return ehCacheManager; } /** * 建立安全认证资源类 * (本身实现的登录和受权认证规则) */ @Bean(name = "shiroRealm") public ShiroRealm shiroRealm(EhCacheManager shiroCacheManager) { ShiroRealm realm = new ShiroRealm(); realm.setCacheManager(shiroCacheManager); //为资源类配置缓存 return realm; } /** * 建立保存记住我信息的Cookie */ @Bean(name = "rememberMeCookie") public SimpleCookie getSimpleCookie() { SimpleCookie simpleCookie = new SimpleCookie(); simpleCookie.setName("rememberMe");//cookie名字 simpleCookie.setHttpOnly(true); //设置cookieHttpOnly,保证cookie安全 simpleCookie.setMaxAge(604800); //保存7天 单位秒 return simpleCookie; } /** * 建立记住我管理器 */ @Bean(name = "rememberMeManager") public CookieRememberMeManager getCookieRememberMeManager(SimpleCookie rememberMeCookie) { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); byte[] cipherKey = Base64.decode("wGiHplamyXlVB11UXWol8g==");//建立cookie秘钥 cookieRememberMeManager.setCipherKey(cipherKey); //存入cookie秘钥 cookieRememberMeManager.setCookie(rememberMeCookie); //存入记住我Cookie return cookieRememberMeManager; } /** * 建立默认的安全管理类 * 整个安全认证流程的管理都由此类负责 */ @Bean(name = "securityManager") public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm,EhCacheManager shiroCacheManager,CookieRememberMeManager rememberMeManager) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //建立安全管理类 defaultWebSecurityManager.setRealm(shiroRealm); //指定资源类 defaultWebSecurityManager.setCacheManager(shiroCacheManager);//为管理类配置Session缓存 defaultWebSecurityManager.setRememberMeManager(rememberMeManager);//配置记住我cookie管理类 return defaultWebSecurityManager; } /** * 得到拦截器工厂类 */ @Bean (name = "authenticationFilter") public AuthenticationFilter authenticationFilter() { return new AuthenticationFilter(); } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,AuthenticationFilter authenticationFilter) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager);//设置SecurityManager,必输 shiroFilterFactoryBean.setLoginUrl("/login");//配置登陆路径(登陆页的路径和表单提交的路径必须是同一个,页面的GET方式,表单的POST方式) shiroFilterFactoryBean.setSuccessUrl("/home");//配置登陆成功页路径 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");//配置没有权限跳转的页面 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/", "anon"); //无需登陆认证和受权就可访问的路径使用anon拦截器 filterChainDefinitionMap.put("/home/**", "user");//须要登陆认证的路径使用authc或user拦截器 filterChainDefinitionMap.put("/user/**", "user,perms[user-jurisdiction]");//须要权限受权的路径使用perms拦截器 filterChainDefinitionMap.put("/admin/**", "user,perms[admin-jurisdiction]");//authc和perms拦截器可同时使用 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);//设置拦截规则 Map<String, Filter> map = new HashMap<String, Filter>(); map.put("authc", authenticationFilter);//自定义拦截器覆盖了FormAuthenticationFilter登陆拦截器所用的拦截器名authc shiroFilterFactoryBean.setFilters(map);//添加自定义拦截器 return shiroFilterFactoryBean; } /** * 注册shiro拦截器 */ @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); //建立代理拦截器,并指定代理shiro拦截器 filterRegistration.addInitParameter("targetFilterLifecycle", "true");//设置拦截器生命周期管理规则。false(默认)由SpringApplicationContext管理,true由ServletContainer管理。 filterRegistration.setEnabled(true);// 激活注册拦截器 filterRegistration.addUrlPatterns("/*");//添加拦截路径 return filterRegistration; } }
package pub.lichao.shiro.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.servlet.http.HttpServletRequest; /** * Controller - 主页 */ @Controller public class HomeController { /** * 进入主页 * @param request * @param redirectAttributes * @return */ @RequestMapping(value = "/home", method = RequestMethod.GET) public String home(HttpServletRequest request, RedirectAttributes redirectAttributes) { return "home"; } /** * 进入无权限提示页 * @param request * @param redirectAttributes * @return */ @RequestMapping(value = "/unauthorized", method = RequestMethod.GET) public String unauthorized(HttpServletRequest request, RedirectAttributes redirectAttributes) { return "unauthorized"; } /** * 进入admin页(用户无此权限) */ @RequestMapping(value = "/admin", method = RequestMethod.GET) public String admin(HttpServletRequest request, RedirectAttributes redirectAttributes) { return "admin"; } }
package pub.lichao.shiro.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.servlet.http.HttpServletRequest; /** * Controller - 登录 */ @Controller public class LoginController { /** * 根路径重定向到登陆页 * @return */ @RequestMapping(value = "/", method = RequestMethod.GET) public String loginForm() { return "redirect:login"; } /** * 进入登陆页面 * @return */ @RequestMapping(value = "/login", method = RequestMethod.GET) public String loginInput(@ModelAttribute("message") String message) { if (message != null && !message.equals("")){ //此处演示一下重定向到此方法时经过addFlashAttribute添加的参数怎么获取 System.out.println("addFlashAttribute添加的参数 :"+ message); } //判断是否已经登陆 或 是否已经记住我 if (SecurityUtils.getSubject().isAuthenticated() || SecurityUtils.getSubject().isRemembered()) { return "redirect:/home"; } else { return "login"; } } /** * 登陆表单提交 * @param request * @param redirectAttributes * @return */ @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(HttpServletRequest request, RedirectAttributes redirectAttributes) { //若是认证未经过得到异常并重定向到登陆页 String message = null; String loginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);//取得登录失败异常 if (loginFailure.equals("pub.lichao.shiro.shiro.CaptchaAuthenticationException")) { message = "验证码错误";//自定义登录认证异常 - 用于验证码错误提示 } else if (loginFailure.equals("org.apache.shiro.authc.UnknownAccountException")) { message = "用户不存在";//未找到帐户异常 }else if (loginFailure.equals("org.apache.shiro.authc.IncorrectCredentialsException")) { message = "密码错误";//凭证(密码)错误异常 } else if (loginFailure.equals("org.apache.shiro.authc.AuthenticationException")) { message = "帐号认证失败";//认证异常 }else{ message = "未知认证错误";//未知认证错误 } //重定向参数传递,可以将参数传递到最终页面 // (用addAttribute的时候参数会写在url中因此要用addFlashAttribute) redirectAttributes.addFlashAttribute("message", message); return "redirect:login"; } /** * 退出登陆 * @param redirectAttributes * @return */ @RequestMapping(value = "/logout", method = RequestMethod.GET) public String logout(RedirectAttributes redirectAttributes) { //调用shiro管理工具类的退出登陆方法 SecurityUtils.getSubject().logout(); redirectAttributes.addFlashAttribute("message", "您已安全退出"); return "redirect:login"; //退出后返回到登陆页 } }
package pub.lichao.shiro.shiro; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.util.WebUtils; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Filter - 自定义登录拦截器 * 继承并重写默认的登陆拦截器 */ public class AuthenticationFilter extends FormAuthenticationFilter { /** * 建立Token */ @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = getUsername(request);//获取用户名 表单name:username String password = getPassword(request);//获取密码 表单name:password boolean rememberMe = isRememberMe(request);//获取是否记住我 表单name:rememberMe String captchaId = WebUtils.getCleanParam(request, "captchaId");//获取验证码id String captcha = WebUtils.getCleanParam(request, "captcha");//获取用户输入的验证码字符 return new CaptchaAuthenticationToken(username, password,captchaId, captcha, rememberMe);//存入本身定义的包含验证码的Token } /** * 登陆验证成功以后 */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { return super.onLoginSuccess(token, subject, request, response); } /** * 当访问被拒绝 */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { //访问被拒绝时默认行为是返回登陆页,可是当使用ajax进行登陆时要返回403错误 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String requestType = request.getHeader("X-Requested-With"); //获取http头参数X-Requested-With if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) { //头参数X-Requested-With存在而且值为XMLHttpRequest说明是ajax请求 response.sendError(HttpServletResponse.SC_FORBIDDEN); //返回403错误 - 执行访问被禁止 return false; }else{ return super.onAccessDenied(servletRequest, servletResponse); } } }
package pub.lichao.shiro.shiro; import org.apache.shiro.authc.AuthenticationException; /** * 自定义登录认证异常 - 用于验证码错误提示 */ public class CaptchaAuthenticationException extends AuthenticationException { }
package pub.lichao.shiro.shiro; import org.apache.shiro.authc.UsernamePasswordToken; /** * Token - 自定义登陆令牌 * 继承并重写默认的登陆令牌 */ public class CaptchaAuthenticationToken extends UsernamePasswordToken { /** * 自定义构造方法 */ public CaptchaAuthenticationToken(String username, String password, String captchaId, String captcha, boolean rememberMe) { super(username, password, rememberMe); this.captcha=captcha; this.captchaId=captchaId; } /** * 自定义参数 */ private String captchaId; //验证码id private String captcha; //录入的验证码字符 public String getCaptchaId() { return captchaId; } public void setCaptchaId(String captchaId) { this.captchaId = captchaId; } public String getCaptcha() { return captcha; } public void setCaptcha(String captcha) { this.captcha = captcha; } }
package pub.lichao.shiro.shiro; /** * 身份信息 */ public class Principal implements java.io.Serializable{ /** 用户ID */ private Long userId; /** 用户名 */ private String username; public Principal(Long userId, String username) { this.userId = userId; this.username = username; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
登陆认证和受权逻辑实现都在这里面算法
/** * @(#)ShiroRealm.java * Description: * Version : 1.0 * Copyright: Copyright (c) 苗方清颜 版权全部 */ package pub.lichao.shiro.shiro; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.Sha256Hash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.ArrayList; import java.util.List; /** * 安全认证资源类 */ public class ShiroRealm extends AuthorizingRealm { /** * 登陆认证(身份验证) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { CaptchaAuthenticationToken authenticationToken = (CaptchaAuthenticationToken) token; //得到登陆令牌 String username = authenticationToken.getUsername(); String password = new String(authenticationToken.getPassword());//将char数组转换成String类型 String captchaId = authenticationToken.getCaptchaId(); String captcha = authenticationToken.getCaptcha(); // 验证用户名密码和验证码是否正确 usernamePasswordAndCaptchaAuthentication(username,password,captchaId,captcha); //建立身份信息类(自定义的) Principal principal = new Principal(1L, username); //认证经过返回认证信息类 return new SimpleAuthenticationInfo(principal, password, getName()); } /** * 受权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //获取当前登陆用户的信息(登陆认证时取得的) Principal principal = (Principal) principals.getPrimaryPrincipal(); //使用登陆信息中存入的userId获取当前用户全部权限 List<String> authorities = getAuthority(principal.getUserId()); //建立受权信息类 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //将权限存入受权信息类 authorizationInfo.addStringPermissions(authorities); return authorizationInfo; } /** * 验证用户名密码和验证码是否正确 * @param username * @param password * @param captchaId * @param captcha * @return */ private void usernamePasswordAndCaptchaAuthentication(String username,String password,String captchaId,String captcha){ //验证验证码是否正确 if(!captchaId.equals("1") || !captcha.equals("yyyy")){ throw new CaptchaAuthenticationException(); //验证码错误时抛出自定义的 验证码错误异常 } //验证用户名是否存在 if(!username.equals("admin")){ throw new UnknownAccountException(); //用户并不存在异常 } //密码加密(SHA256算法) String salt = "c1bac4173f3df3bf0241432a45ac3922";//密言通常由系统为每一个用户随机生成 String sha256 = new Sha256Hash(password, salt).toString(); //使用sha256进行加密密码 //验证密码是否正确 if(!sha256.equals("aa07342954e1ca7170257e74515139cc27710ff703e6fee784d0a4ea1e09f9da")){ throw new IncorrectCredentialsException();//密码错误异常 } } /** * 获取用户权限 * @param userId * @return */ private List<String> getAuthority(Long userId){ List<String> authority = new ArrayList<String>(); if(userId.equals(1L)){ authority.add("user-jurisdiction"); // authority.add("admin-jurisdiction"); } return authority; } }
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="shiro-ehcache"> <!-- 缓存文件存放目录 --> <!-- java.io.tmpdir表明操做系统默认的临时文件目录,不一样操做系统路径不一样 --> <!-- windows 7 C:\Users\Administrator\AppData\Local\Temp --> <!-- linux /tmp --> <diskStore path="${java.io.tmpdir}/shiro/ehcache"/> <!-- 设置缓存规则--> <!-- maxElementsInMemory:缓存文件在内存上最大数目 maxElementsOnDisk:缓存文件在磁盘上的最大数目 eternal:缓存是否永不过时。true永不过时,false会过时 timeToIdleSeconds :缓存最大空闲时间,空闲超过此时间则过时(单位:秒)。当eternal为false时有效 timeToLiveSeconds :缓存最大的生存时间,从建立开始超过这个时间则过时(单位:秒)。当eternal为false时有效 overflowToDisk:若是内存中数据超过内存限制,是否缓存到磁盘上 diskPersistent:是否在磁盘上持久化缓存,系统重启后缓存依然存在 --> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" diskPersistent="false"/> </ehcache>
loginspring
<!DOCTYPE html> <html lang="en" class="no-js"> <head> <title>管理平台</title> </head> <body style="text-align:center"> <div style="margin:0 auto;"> <form action="/login" method="post"> <h3>管理平台登陆</h3> <label>用户名:</label> <input type="text" placeholder="请输入用户名" name="username" id="username" value="admin"/><br><br> <label>密 码:</label> <input type="password" placeholder="请输入密码" name="password" id="password" value="111111"/><br><br> <label>验证码:</label> <input type="text" placeholder="请输入验证码" name="captcha" id="captcha" value="yyyy"/><br><br> <input type="hidden" name="captchaId" id="captchaId" value="1"/> <label>保持登陆:</label> <input type="checkbox" name="rememberMe" id="rememberMe" />(保持7天)<br><br> <button type="submit">登陆</button><br><br> <#if message??><span style="color: red" >${message}</span></#if> </form> </div> </body> </html>
home apache
<!DOCTYPE html> <html lang="en" class="no-js"> <head> <title>管理平台</title> </head> <body style="text-align:center"> <h1>欢迎登陆管理平台!</h1> <br><br> <a href="logout">退出登陆</a> <br><br> <@shiro.hasPermission name = "user-jurisdiction"> 用户拥有user-jurisdiction权限才能看见此内容! </@shiro.hasPermission> <br><br> <button onclick="javascript:window.location.href='admin'"> 进入到admin页(须要有权限) </button> </body> </html>
admin
<!DOCTYPE html> <html lang="en" class="no-js"> <head> <title>管理平台</title> </head> <body style="text-align:center"> <h1>用户须要有admin-jurisdiction权限才能看到此页内容</h1><br><br> <a href="javascript:history.go(-1)">返回</a> </body> </html>
unauthorized
<!DOCTYPE html> <html lang="en" class="no-js"> <head> <title>管理平台</title> </head> <body style="text-align:center"> 你没有访问此功能的权限! <a href="javascript:history.go(-1)">返回</a> </body> </html>