Shiro
官网介绍主要实现四个功能:
-
Authentication: Sometimes referred to as ‘login’, this is the act of proving a user is who they say they are.
-
Authorization: The process of access control, i.e. determining ‘who’ has access to ‘what’.
-
Session Management: Managing user-specific sessions, even in non-web or EJB applications.
-
Cryptography: Keeping data secure using cryptographic algorithms while still being easy to use.
2.架构
三个概念:
1.Subject:当前用户,可以是一个人也可是服务,表示与当前软件交互的任何事件
2.SecurityManager:管理所有Subject,为Shiro架构的核心
3.Realms:用于进行权限信息的验证,由自己实现
3.配置
1.编写ShiroConfig配置类,用到注解@Configuration交由spirng管理,通过url来进行过滤和权限划分
首先new出ShiroFilterFactoryBean 将securityManager添加进去,再设置登录路径、成功路径及无权限登录路径
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射 shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setSuccessUrl("/index/main"); // 设置无权限时跳转的 url; shiroFilterFactoryBean.setUnauthorizedUrl("/error/404");
添加url规则Map
// 设置拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //游客,开发权限 filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/druid/**", "anon"); //开放登陆接口 filterChainDefinitionMap.put("/main", "anon"); filterChainDefinitionMap.put("/login", "authc"); filterChainDefinitionMap.put("/index/logout", "logout"); //其余接口一律拦截 //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 filterChainDefinitionMap.put("/**", "user,sysUser"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
2.注入Realm
@Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); customRealm.setCachingEnabled(false); return customRealm; }
其中有个加密方法
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
/*授权匹配 */
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
散列两次的加密方式,参考
3.注入SecurityManager
/** * 注入 securityManager */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setAuthenticator(authenticator()); // 设置realm. securityManager.setRealms(getRealms()); return securityManager; }
这个也是将Realms集合添加进去
4.自定义Realm
package com.btw.config.shiro; import com.btw.entity.sys.User; import com.btw.service.sys.UserService; import org.apache.shiro.authc.*; 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 org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.util.ByteSource; import javax.annotation.Resource; public class CustomRealm extends AuthorizingRealm { @Resource private UserService userService; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; // 从数据库获取对应用户名密码的用户 User user = userService.findUserByLoginName(token.getUsername()); String url = new String((char[]) token.getCredentials()); if (null == user) { throw new AccountException("用户名不正确"); } if (user.isLocked()) { throw new LockedAccountException(); //帐号锁定 } ByteSource credentialsSalt = ByteSource.Util.bytes(user.getLoginName());//使用账号作为盐值 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user.getLoginName(),//用户名 user.getPasswd(),//密码 credentialsSalt, getName() //realm name ); return authenticationInfo; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(userService. findRoles(username)); //角色 authorizationInfo.setStringPermissions(userService.findPermissions(username)); //权限 return authorizationInfo; } public void removeUserAuthorizationInfoCache(String username) { SimplePrincipalCollection pc = new SimplePrincipalCollection(); pc.add(username, super.getName()); super.clearCachedAuthorizationInfo(pc); } }
第一个doGetAuthenticationInfo()方法为登录认证的实现
该方法主要执行以下操作:
-
1、检查提交的进行认证的令牌信息
-
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
-
3、对用户信息进行匹配验证。
-
4、验证通过将返回一个封装了用户信息的
AuthenticationInfo
实例。 -
5、验证失败则抛出
AuthenticationException
异常信息。
第二个doGetAuthorizationInfo()方法为授权的实现
set 集合:roles 是从数据库查询的当前用户的角色,stringPermissions 是从数据库查询的当前用户对应的权限
就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”);
就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问,如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”);
就说明访问/add
这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。
5.登录接口
这个主要是处理异常的相关信息
@Controller @Slf4j public class LoginController { private final static String errorAttributeName = "shiroLoginFailure"; @Autowired private UserService userService; // 五分钟 private ExpiryMap<String, Integer> resetMap = new ExpiryMap<>(1000 * 60 * 5); @RequestMapping(value = "/login") public String showLoginForm(HttpServletRequest req, Model model, @RequestParam(value = errorAttributeName, required = false) String errorMsg) { String exceptionClassName = (String) req.getAttribute(errorAttributeName); String error; if (UnknownAccountException.class.getName().equals(exceptionClassName)) { error = "用户名/密码错误"; //账户不存在 } else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { error = "用户名/密码错误"; } else if (ExcessiveAttemptsException.class.getName().equals(exceptionClassName)) { error = "密码错误次数已达上限(3次),请稍后再试"; } else if (exceptionClassName != null) { error = "用户名/密码错误"; } else { error = errorMsg; } model.addAttribute("error", error); return "login"; } private boolean isAuthenticated() { return SecurityUtils.getSubject().isAuthenticated(); } }
6.前置controller
@Controller public class ForwardPageController { private final String NULL_PAGE_URL = "error/404"; @RequestMapping("/main") public String mainPage(HttpServletRequest request) { String forwardUrl = "forward:/"; String userId = request.getParameter("userId"); String url = request.getParameter("page"); // 查找用户 if (StringUtils.isNotBlank(userId)) { try { // 从SecurityUtils里边创建一个 subject Subject subject = SecurityUtils.getSubject(); String decrypt = AESUtils.getDefaultNoPadding().decrypt(userId).trim(); // String decrypt = userId; // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(decrypt, url); // 执行认证登陆 subject.login(token); boolean authenticated = subject.isAuthenticated(); if (authenticated) { subject.getSession().setAttribute("page", url); forwardUrl = forwardUrl + url; return forwardUrl; } } catch (Exception e) { e.printStackTrace(); } } forwardUrl = forwardUrl + NULL_PAGE_URL; return forwardUrl; } }
前台传递两个参数,一个page为路径,还有一个userId为用户名
创建Subject对象,用AES加密算法将用户名加密,然后创建令牌,获取Page参数的Url,跳转页面
public static void main(String[] args) {
String hashAlgorithmName = "md5";//加密方式
Object crdentials = "123456";//密码原值
Object salt = "admin";//盐值
int hashIterations = 2;//散列次数
SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, crdentials, salt, hashIterations);
System.out.println(simpleHash);
}