Apache Shiro 是一个Java安全框架,用于 身份验证、授权、密码和会话管理
核心组件:Subject、SecurityManager、Realmes。
Subject代表当前用户安全操作。
SecurityManager管理所有用户安全操作。Facade外观模式;
Realme负责登录和授权(访问控制)。
shiro内置了大量安全数据源的Realme,如LDAP、JDBC、类似INI的文本配置及属性文件等。
1,简单使用
1)环境搭建
建立SpringBoot项目,引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
2)application.prop配置thymeleaf
# THYMELEAF (ThymeleafAutoConfiguration) # 开启模板缓存(默认值: true ) # 开发阶段false不然看不到实时页面 spring.thymeleaf.cache=false # 关闭h5语法验证 spring.thymeleaf.mode=LEGACYHTML5
3)新建h5页面
在resource/templates下
login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="login2" method="post"> 账号 <input type="text" name="username"> <br/> 密码 <input type="text" name="password" id="password"> <br/> <input type="submit" value="登录" id="loginBtn"> </form> <span th:text="${errorMessage}"/> </body> </html>
success.html
noPermission.html
固定页面
4)config
//spring配置类 @Configuration public class ShiroConfig { @Bean public SecurityManager getSecurityManager(Realm realm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(realm); return manager; } //自定义Realm,用来认证和授权 @Bean public Realm getRealm() { // CustomRealm realm = new CustomRealm(); //测试1 CustomAuthRealm realm = new CustomAuthRealm();//测试2 return realm; } //过滤器bean,配置shiro相关规则拦截 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); bean.setLoginUrl("/"); //需要登录时跳转的地址 bean.setSuccessUrl("/success"); //登陆成功跳转地址 bean.setUnauthorizedUrl("/noPermission");//没权限跳转地址 //权限拦截规则 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/login", "anon"); //anon表示某个请求不需认证 filterMap.put("/login2", "anon"); //anon表示某个请求不需认证 filterMap.put("/logout", "logout"); //登出后跳转 filterMap.put("/admin/**", "authc");//authc需要认证 filterMap.put("/user/**", "authc");//authc需要认证 filterMap.put("/**", "authc");//剩余的也都需要登录 bean.setFilterChainDefinitionMap(filterMap); return bean; } }
其中自定义的Realm配置
public class CustomRealm implements Realm { @Override public String getName() { return null; } @Override public boolean supports(AuthenticationToken authenticationToken) { return false; } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { return null; } }
包含了简单鉴权的Realm
public class CustomAuthRealm extends AuthenticatingRealm { /** * 用户认证 * @param authenticationToken 用户身份,存有账号密码 * @return 登陆成功后身份证明 * @throws AuthenticationException 认证失败异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String name = token.getUsername(); String pwd = new String(token.getPassword()); System.out.println(name +" "+pwd); if("11".equals(name)){ throw new UnknownAccountException(); //未知账号 } if("22".equals(name)){ throw new LockedAccountException(); //账号锁定异常 } //数据库中账号、密码、当前Realm名字 return new SimpleAuthenticationInfo(name,"123456",getName()); //创建认证对象,认证成功 } }
5)最后,定义controller发布api
@Controller public class TestController { @RequestMapping("/") public String index() { return "login"; } @RequestMapping("/login2") public String login2(String username, String password, Model model) { Subject subject = SecurityUtils.getSubject();//权限操作对象 //先登出,避免缓存问题 subject.logout(); //没有登陆过 if (!subject.isAuthenticated()) { UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password); //身份令牌 try { subject.login(usernamePasswordToken); } catch (UnknownAccountException e) { model.addAttribute("errorMessage", "账号错误"); return "login"; } catch (LockedAccountException e) { model.addAttribute("errorMessage", "账号被锁定"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("errorMessage", "密码错误"); return "login"; } catch (AuthenticationException e) { model.addAttribute("errorMessage", "认证失败"); e.printStackTrace(); return "login"; } } return "redirect:/success"; } @RequestMapping("/login") public String login() { return "redirect:/success"; } @RequestMapping("/logout") public String logout() { SecurityUtils.getSubject().logout(); return "redirect:/"; } @RequestMapping("/success") public String success() { return "success"; } @RequestMapping("/noPermission") public String noPermission() { return "noPermission"; } @RequestMapping("/admin/test") public @ResponseBody String adminTest() { return "/admin/test 请求"; } @RequestMapping("/user/test") public @ResponseBody String userTest() { return "/user/test 请求"; } }
6)测试
请求 http://localhost:8080
2,密码加密
1)测试Realm加密设置
/** * 用户认证 * @param authenticationToken 用户身份,存有账号密码 * @return 登陆成功后身份证明 * @throws AuthenticationException 认证失败异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String name = token.getUsername(); String pwd = new String(token.getPassword()); System.out.println(name +" "+pwd); if("11".equals(name)){ throw new UnknownAccountException(); //未知账号 } if("22".equals(name)){ throw new LockedAccountException(); //账号锁定异常 } //加密应该在客户端上加密后传输到服务器,不应该在服务器端加密 //数据来源密码 加密 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("MD5"); matcher.setHashIterations(2); setCredentialsMatcher(matcher); //设置加密规则 //数据库中存储的密码 加密 模拟 SimpleHash md5 = new SimpleHash("MD5", "123456", "", 2); //数据库中账号、密码、当前Realm名字 // return new SimpleAuthenticationInfo(name,"123456",getName()); //创建认证对象,认证成功 return new SimpleAuthenticationInfo(name,md5,getName()); //创建认证对象,认证成功 }
2)浏览器加密传输到服务器
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>login</title> <script th:src="@{|/js/jquery-1.11.3.min.js|}"></script> <script th:src="@{|/js/jQuery.md5.js|}"></script> <script> $(function () { $("#loginBtn").bind("click", function () { alert(1); var md5Str = $.md5($("#password").val()); alert(md5Str); $("#md5Password").val(md5Str); }) }); // $(document).ready(function(){ // $("loginBtn").click(function(){ // alert(2); // }); // }); </script> </head> <body> <form action="login2" method="post"> 账号 <input type="text" name="username"> <br/> 密码 <input type="text" id="password"> <br/> <input type="submit" value="登录" id="loginBtn"> <input type="hidden" name="password" id="md5Password"/> </form> <span th:text="${errorMessage}"/> </body> </html>
3,角色分配
再重新定义一个Realm,并使用
/** * 既能认证,也能授权 * * @author senrsl * @ClassName: CustomAuth2Realm * @Package: dc.test.shiro.realm * @CreateTime: 2022/7/5 17:26 */ public class CustomAuth2Realm extends AuthorizingRealm { /** * 用户认证 * * @param authenticationToken 用户身份,存有账号密码 * @return 登陆成功后身份证明 * @throws AuthenticationException 认证失败异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String name = token.getUsername(); String pwd = new String(token.getPassword()); System.out.println(name + " " + pwd); if ("11".equals(name)) { throw new UnknownAccountException(); //未知账号 } if ("22".equals(name)) { throw new LockedAccountException(); //账号锁定异常 } // //加密应该在客户端上加密后传输到服务器,不应该在服务器端加密 // //数据来源密码 加密 // HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); // matcher.setHashAlgorithmName("MD5"); // matcher.setHashIterations(2); // setCredentialsMatcher(matcher); //设置加密规则 // // // //数据库中存储的密码 加密 模拟 // SimpleHash md5 = new SimpleHash("MD5", "123456", "", 2); //数据库中账号、密码、当前Realm名字 // return new SimpleAuthenticationInfo(name,"123456",getName()); //创建认证对象,认证成功 // return new SimpleAuthenticationInfo(name,md5,getName()); //创建认证对象,认证成功 return new SimpleAuthenticationInfo(name, "e10adc3949ba59abbe56e057f20f883e", getName()); //创建认证对象,认证成功 } /** * 用户授权 * 用户认证通过后 授权 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("用户授权方法"); Object primaryPrincipal = principalCollection.getPrimaryPrincipal();//获取用户账号,根据账号从数据库读取角色 //每次点击都去请求数据库,效率问题 Set<String> setRoles = new HashSet<>(); //模拟角色配置 //admin账号有admin权限 //http://localhost:8080/admin/test if ("admin".equals(primaryPrincipal)) { setRoles.add("admin"); setRoles.add("users"); } //user账号只有user权限 //http://localhost:8080/user/test if ("user".equals(primaryPrincipal)) { setRoles.add("users"); } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(setRoles); //添加角色关系 return info; } }
请求地址增加角色过滤
// filterMap.put("/admin/**", "authc");//authc需要认证 filterMap.put("/admin/**", "authc,roles[admin]");//授权admin权限
使用success.html测试
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 登陆成功 <br/> <br/> <a href="/logout">登出</a> <br/> <a th:href="@{|/admin/test|}">需要有admin权限</a> <br/> <a th:href="@{|/user/test|}">需要有user权限</a> <br/> </body> </html>
用户登陆后,根据角色权限,判断跳转admin或user是否有权限。
4,基于注解的角色配置
注释掉之前的权限配置
//改用注解 // filterMap.put("/admin/**", "authc,roles[admin]");//授权admin权限 // filterMap.put("/user/**", "authc");//authc需要认证
开启注解支持
ShroConfig添加
/** * 开启shiro注解支持 * 如 @RequireRoles()、@RequirPermissions() * @return */ @Bean public DefaultAdvisorAutoProxyCreator creator(){ DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } /** * 开启AOP支持 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor advisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
controller添加注解
@RequiresRoles(value = {"admin"}) //必须要有什么样的角色 value一个或多个角色名,logical AND、OR @RequestMapping("/admin/test") public @ResponseBody String adminTest() { return "/admin/test 请求"; } @RequiresRoles(value = {"user"}) @RequestMapping("/user/test") public @ResponseBody String userTest() { return "/user/test 请求"; } //自定义异常拦截 @ExceptionHandler(value = {AuthorizationException.class}) public String permissionError(Throwable throwable) { return "noPermission"; //noPermission.html }
5,权限配置
realm中除增加角色配置外,还可以增加权限配置
//权限配置 Set<String> setPermission = new HashSet<>(); if("admin".equals(primaryPrincipal)){ setPermission.add("admin:add");//只是命名风格,表示 admin下add功能 } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(setRoles); //添加角色关系 info.setStringPermissions(setPermission); //添加权限
controller中使用
@RequiresRoles(value = {"admin"}) //必须要有什么样的角色 value一个或多个角色名,logical AND、OR @RequiresPermissions(value = {"admin:add"}) //当前用户是否有权限 @RequestMapping("/admin/add") public @ResponseBody String adminAdd() { // //除了给予配置的权限验证、注解的权限验证,还支持基于方法的权限验证 // Subject subject = SecurityUtils.getSubject(); // subject.checkRole();//验证角色 // subject.checkPermission();//验证权限 return "/admin/add 请求"; }
6,集成Shiro标签到Thymleaf
依赖
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
shiroConfig添加bean
/** * themeleaf 扩展 shiro * @return */ @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); }
添加命名空间
基于属性使用
基于标签使用
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 登陆成功 <br/> <br/> <a href="/logout">登出</a> <br/> <a th:href="@{|/admin/test|}" shiro:hasRole="admin">需要有admin权限</a> <br/> <a th:href="@{|/admin/add|}">需要有admin:add权限</a> <br/> <shiro:hasRole name="user"> <a th:href="@{|/user/test|}">需要有user权限</a> <br/> </shiro:hasRole> </body> </html>
--
senRsl
2022年07月05日10:15:49
senRsl
2022年07月05日10:15:49
没有评论 :
发表评论