1、shiro的三个核心概念:
1)Subject:代表当前正在执行操作的用户,但Subject代表的可以是人,也可以是任何第三方系统帐号。当然每个subject实例都会被绑定到SercurityManger上。
2)SecurityManger:SecurityManager是Shiro核心,主要协调Shiro内部的各种安全组件,这个我们不需要太关注,只需要知道可以设置自定的Realm。
3)Realm:用户数据和Shiro数据交互的桥梁。比如需要用户身份认证、权限认证。都是需要通过Realm来读取数据。
2、springboot中集成shiro相对简单,只需要两个类:一个是ShiroConfig类,一个是自定义Realm类。
1)ShiroConfig类:shiro的一些配置,相对于之前的xml配置。包括:ShiroFilter
的配置,密码加密的算法,支持注解的配置等功能。
2)自定义Realm类:继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限认证)、doGetAuthenticationInfo(身份认证)这两个方法。
3、demo
项目结构:
依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
ShiroConfig
package com.oy; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; /** * @author oy * @date 2019年8月10日 下午4:50:55 * @version 1.0.0 */ @Configuration public class ShiroConfig { /************************* shiroFilter配置 start *************************/ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setUnauthorizedUrl("/unauth"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/user/login", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/front/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); filterChainDefinitionMap.put("/user/**", "authc"); // 剩余的都需要认证 // 这行代码必须放在所有权限设置的最后,不然会导致所有url都被拦截 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(myRealm()); return defaultSecurityManager; } @Bean public MyRealm myRealm() { MyRealm myRealm = new MyRealm(); return myRealm; } /************************* shiroFilter配置 end *************************/ /************************* 开启shiro注解配置 start *************************/ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions), * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证. * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 */ @Bean @DependsOn({ "lifecycleBeanPostProcessor" }) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } /************************* 开启shiro注解配置 end *************************/ }
MyRealm
package com.oy; import java.util.HashSet; import java.util.Set; 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.SimpleAuthenticationInfo; 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 com.oy.model.User; import com.oy.service.UserService; public class MyRealm extends AuthorizingRealm { @Resource private UserService userService; /** * 认证:身份认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.getUserByUsername(username); if (user != null) { AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); return authcInfo; } else { return null; } } /** * 授权:权限认证 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //String userName = (String) principals.getPrimaryPrincipal(); //authorizationInfo.setRoles(userService.getRoles(userName)); //authorizationInfo.setStringPermissions(userService.getPermissions(userName)); Set<String> stringSet = new HashSet<>(); stringSet.add("user:view"); stringSet.add("user:edit"); authorizationInfo.setStringPermissions(stringSet); return authorizationInfo; } }
IndexController
package com.oy.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.oy.util.CryptographyUtil; /** * @author oy * @date 2019年8月10日 下午6:21:58 * @version 1.0.0 */ @Controller public class IndexController { @RequestMapping("/login") public String login() { return "login.html"; } @RequestMapping("/user/login") @ResponseBody public String doLogin(@RequestParam(value = "username", required = false) String username, @RequestParam(value = "password", required = false) String password) { System.out.println("username=" + username + ", password=" + password); // 从SecurityUtils里边创建一个 subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备 token(令牌) //UsernamePasswordToken token = new UsernamePasswordToken(username, password); UsernamePasswordToken token = new UsernamePasswordToken(username, CryptographyUtil.md5(password, "abc123")); // 执行认证登陆 try { subject.login(token); } catch (UnknownAccountException uae) { return "未知账户"; } catch (IncorrectCredentialsException ice) { return "密码不正确"; } catch (LockedAccountException lae) { return "账户已锁定"; } catch (ExcessiveAttemptsException eae) { return "用户名或密码错误次数过多"; } catch (AuthenticationException ae) { return "用户名或密码不正确!"; } if (subject.isAuthenticated()) { return "登录成功"; } else { token.clear(); return "登录失败"; } } @RequiresPermissions("user:view") @GetMapping("/user") @ResponseBody public String userList() { return "user list"; } @RequiresPermissions("book:view") @GetMapping("/book") @ResponseBody public String bookList() { return "book list"; } @RequestMapping("/unauth") public String unauth() { return "unauth.html"; } }
login.html
<body> <h2>login页面</h2> <form action="/user/login" method="post"> username: <input type="text" name="username" value=""/></br></br> password: <input type="text" name="password" value=""/></br> <input type="submit" value="submit"/> </form> </body>
CryptographyUtil
package com.oy.util; import org.apache.shiro.codec.Base64; import org.apache.shiro.crypto.hash.Md5Hash; public class CryptographyUtil { /** * base64加密 * @param str * @return */ public static String encBase64(String str) { return Base64.encodeToString(str.getBytes()); } /** * base64解密 * @param str * @return */ public static String decBase64(String str) { return Base64.decodeToString(str); } /** * Md5加密 * @param str * @param salt * @return */ public static String md5(String str, String salt) { return new Md5Hash(str, salt).toString(); } public static void main(String[] args) { String password = "123456"; System.out.println("Base64加密:" + CryptographyUtil.encBase64(password)); System.out.println("Base64解密:" + CryptographyUtil.decBase64(CryptographyUtil.encBase64(password))); // a6f70dedd698be90addd35abe38d3876 System.out.println("Md5加密:" + CryptographyUtil.md5(password, "abc123")); } }
参考资料:
2)SpringBoot搭建基于Apache Shiro的权限管理功能
3)shiro无权限,不跳转到指定页面。setUnauthorizedUrl无效