Shiro介绍
- Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
主要功能
Shiro的三个核心组件:Subject,SecurityManager和Realms
Subject:指的是当前操作的用户,可以是人,可以是机器或者第三方的进程,与软件交互的东西。SubjectManager:是shiro框架的核心shiro通过SubjectManager来管理内部组件的实例,并提供安全管理的各种服务。Realm:Realm充当shiro与安全数据之间的桥梁,简单地说就是当用户执行认证(登录)授权(访问控制)会从Realm中查找用户及其权限信息,配置Realm可以是一个也可以是多个。
Spring Boot集成shiro
创建spring boot web工程
添加shiro依赖包
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
配置Shiro
@Configuration public class ShiroConfig { //配置一个自定义的Realm 最后将这个bean返回到 完成认证及授权的具体对象 @Bean public Realm myRealm(){ return new MyRealm(); } @Bean public SecurityManager securityManager(Realm myRealm){ //设置一个Realm 这个Realm最终这个Ream是我们最终完成认证及授权的具体对象 DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm); return defaultWebSecurityManager; } //将shiro的过滤器定义到spring的上下文中 利用这个类来完成对请求的各种拦截 @Bean public ShiroFilterFactoryBean filterFactoryBean(SecurityManager securityManager){ //创建拦截器用于拦截我们的用户请求 ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //设置shiro的安全管理 设置管理时会指定某个Realm来完成权限的分配 factoryBean.setSecurityManager(securityManager); factoryBean.setLoginUrl("/");//设置没有登录后的跳转页 factoryBean.setSuccessUrl("/success");//设置登录成功后 factoryBean.setUnauthorizedUrl("/noperms");//没有权限的跳转页面 LinkedHashMap<String,String> chain = new LinkedHashMap(); /*登出请求用于shiro清空会话 自动返回到未登录的页面*/ chain.put("/logout","logout"); //以admin开头的请求必须登录后才能访问 chain.put("/login","anon");//用户登录请求不需要登录就能访问 //roles[admin] 用于指定以admin开头的请求必须有admin 的角色才能访问 chain.put("/admin/**","authc,roles[admin]"); chain.put("/user/**","authc,roles[user]"); chain.put("/js/**","anon"); chain.put("/salt","anon"); chain.put("/**","authc"); factoryBean.setFilterChainDefinitionMap(chain);//不是普通的map集合 return factoryBean; } }
配置MyReam类**AuthenticatingRealm 专门用于完成身分验证的抽象父类*AuthorizingRealm 完成身份验证 及权限分配
public class MyRealm extends AuthorizingRealm {
//用户身份认证的具体方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
/**
* Shiro 的认证方法我们需要在这个方法中来获取用户的信息(从数据库中)
* @param authenticationToken 用户登录时的 Token(令牌),这个对象中将存放着我们用户在浏览器中存放的账号和密码
* @return 返回一个 AuthenticationInfo 对象,这个返回以后 Shiro 会调用这个对象中的一些方法来完成对密码的验证 密码是由 Shiro进行验证是否合法
* @throws AuthenticationException 如果认证失败 Shiro 就会抛出AuthenticationException
* 我们也可以手动自己抛出这个 AuthenticationException以及它的任意子异常类不通的异常类型可以认证过程中的不通错误情况我们需要根据
* 异常类型来为用户返回特定的响应数据
* AuthenticationException 异常的子类 可以我们自己抛出
* AccountException 账号异常 可以我们自己抛出
* UnknownAccountException 账号不存在的异常 可以我们自己抛出
* LockedAccountException 账号异常锁定异常 可以我们自己抛出
* IncorrectCredentialsException 密码错误异常 这个异常会在 Shiro 进行
密码验证是抛出
*/
//获取用户的身份令牌
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();//获取用户在页面中输入的账号
if (!"admin".equals(username) && !"zxp".equals(username) && !"lishi".equals(username)){
throw new UnknownAccountException("账号输入错误");
}else if ("zxp".equals(username)){
throw new LockedAccountException("账号冻结");
}
Subject subject = SecurityUtils.getSubject();
//用session来获取随机盐
String randomsalt = (String) subject.getSession().getAttribute("randonsalt");
String dbpassword = "e10adc3949ba59abbe56e057f20f883e";
//使用随机盐对数据库中的密码进行加密
String password = new SimpleHash("MD5",dbpassword,randomsalt).toString();
//返回验证密码的对象
return new SimpleAuthenticationInfo(username,password,this.getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取触发权限认证的当前用户账号
String username = (String) principalCollection.getPrimaryPrincipal();
//根据账号来做判断 设置权限
Set<String> roles = new HashSet<>();
if ("admin".equals(username)){
roles.add("admin");
roles.add("user");
}else if ("lishi".equals(username)){
roles.add("user");
}
SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();
simpleAuthenticationInfo.setRoles(roles);
return simpleAuthenticationInfo;
}
}
TestController
@Controller public class testcontroller { @RequestMapping("/") public String index(){ return "index"; } @RequestMapping("/login") public String login(String username, String password, Model model){ //获取当前的操作对象 可以是人 也可以是第三方 Subject subject = SecurityUtils.getSubject(); //定义一个token 用于存放用户登录的账号密码 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password); //判断当前用户是否完成认正登录如果如果写了这个if用户无法重复登录 在shiro中一旦用户登录后 shiro会将用户的信记录到subject中故 isAuthenticated() 返回的是TRUE /* *让用户可以重复登录 * 1.不写if 但会增加服务器的压力 * 2.调用subject中的logout用于退出当前的登录状态 清空会话中的所有数据 * 3.在页面中编写一个用户安全退出的请求 * */ if (subject.isAuthenticated()){ return "success"; } try { subject.login(usernamePasswordToken); }catch (UnknownAccountException e){ model.addAttribute("errormsg","账号不存在"); return "index"; }catch (LockedAccountException e){ model.addAttribute("errormsg","账号冻结"); return "index"; }catch (IncorrectCredentialsException e){ model.addAttribute("errormsg","账号密码错误"); return "index"; }catch (AuthenticationException e){ model.addAttribute("errormsg","账号不存在"); return "index"; } return "success"; } @RequestMapping("/noperms") public String noperms(){ return "noperms"; } @RequestMapping("/admin/test") public @ResponseBody String adminTest (){ return "这是admin的test"; } @RequestMapping("/user/test") public @ResponseBody String UserTest(){ return "这是user的test"; } @RequestMapping("/salt") public @ResponseBody String getrandomSalt(HttpSession httpSession){ String random = UUID.randomUUID().toString(); String randomsalt = DigestUtils.md5DigestAsHex(random.getBytes());//spring 框架下的一个加密工具 //将随机生辰成的盐加入到session当中 httpSession.setAttribute("randonsalt",randomsalt); return randomsalt; } }
index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script th:src="@{/js/jquery-3.4.1.min.js}" type="text/javascript"></script> <script th:src="@{/js/JQuery.md5.js}" type="text/javascript"></script> </head> <body> <form action="/login" method="post" id="loginsubmit"> 账号<input type="text" name="username"> 密码<input type="text" id="password"> <input type="text" name="password" id="md5password"> <input type="button" value="登录" id="log"> </form> <span style="color: red">[[${errormsg}]]</span> <script> $(function () { $("#log").bind("click",function () { $.get("/salt","",function (data) { var V_password = $("#password").val(); //用MD5 对密码加密一次 V_password = $.md5(V_password); //用随机盐对 MD5 加密后的密码再加密一次 $("#md5password").val($.md5(data + V_password)) $("#loginsubmit").submit()} , "text") }) }) </script> </body> </html>
noporms.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>对不起 你没有操作权限</h1> </body> </html>
success.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录成功!</title> </head> <body> <h1>登录成功</h1> <!--logout用于退出当前用户的登录的状态 不用定义在controller中定义在过滤器当中--> <a href="/logout">安全退出</a> <a href="/admin/test">Admintest</a> <a href="/user/test">UserTest</a> </body> </html>