分布式 shiro 权限验证
- 2022 年 4 月 12 日
本文字数:3462 字
阅读完需:约 11 分钟
对于非前后端分离的后台管理系统权限验证,shiro 做为一个轻量级的权限验证框架,在很多以前的项目中会被使用。在新项目中一般会使用 spring security,Spring 提供的框架支持度较好。Shiro 的常用注解 @RequiresPermissions @RequiresRoles @RequiresUser。
搭建 shiro 项目
引入依赖 shiro,reddisHttpSession, 此栗子用的 thymeleaf
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
创建 shiro 的配置类, 必须注入 UserRealm,配置拦截的路径,密码验证 HashedCredentialsMatcher
@Configuration
public class ShiroConfig {
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(this.credentialsMatcher());
return userRealm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/captcha", "anon");
chainDefinition.addPathDefinition("/logout", "anon");
chainDefinition.addPathDefinition("/layuiadmin/**", "anon");
chainDefinition.addPathDefinition("/druid/**", "anon");
chainDefinition.addPathDefinition("/api/**", "anon");
chainDefinition.addPathDefinition("/login", "anon");
chainDefinition.addPathDefinition("/**", "authc");
return chainDefinition;
}
@Bean
public HashedCredentialsMatcher credentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("SHA-256");
credentialsMatcher.setStoredCredentialsHexEncoded(false);
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
@Bean
public SessionsSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(this.userRealm());
return securityManager;
}
}
UserRealm 类实现用户的认证及授权两个接口,可以从数据库获取用户账号信息进行验证登录,获取权限信息设置 permissions、role。
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User)SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet();
Set<String> permissions = new HashSet();
if ("admin".equals(user.getUserName())) {
roles.add("admin");
permissions.add("op:write");
} else {
roles.add("user");
permissions.add("op:read");
}
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String)authenticationToken.getPrincipal();
User user = new User();
user.setUserName("admin");
String password = "123456";
String salt = "salt";
int hashIterations = 1024;
String encodedPassword = (new SimpleHash("SHA-256", password, Util.bytes(salt), hashIterations)).toBase64();
user.setPassword(encodedPassword);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), Util.bytes(salt), this.getName());
return authenticationInfo;
}
写一个简单的页面
登录页面 login.html 及 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Shiro Login</title>
</head>
<body>
<h3>Home Login</h3>
<div>port: <span>[[${#httpServletRequest.getServerPort()}]]</span></div>
<div>session: <span>[[${#httpServletRequest.getSession().getId()}]]</span></div>
<div>time: <span>[[${#dates.format(new java.util.Date().getTime(), 'yyyy-MM-dd hh:mm:ss')}]]</span></div>
<form method="post" action="/login">
<div><label for="userName">UserName:</label><input type="text" name="userName" value="admin" id="userName"></div>
<div><label for="password">Password:</label><input type="password" name="password" value="123456" id="password"></div>
<div><input type="submit" value="Submit"></div>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Shiro Session</title>
</head>
<body>
<h3>Shiro Session home</h3>
<div>port: <span>[[${#httpServletRequest.getServerPort()}]]</span></div>
<div>session: <span>[[${#httpServletRequest.getSession().getId()}]]</span></div>
<div>time: <span>[[${#dates.format(new java.util.Date().getTime(), 'yyyy-MM-dd hh:mm:ss')}]]</span></div>
</body>
</html>
登录逻辑进行处理
@RequestMapping(value = "/login",method = {RequestMethod.GET,RequestMethod.POST})
public String doLogin (@RequestParam(required = false) String userName, @RequestParam(required = false) String password,
@RequestParam(required = false) String easyCaptcha, HttpServletRequest request) {
Object principal = SecurityUtils.getSubject().getPrincipal();
if (principal != null) {
return "index";
}
if (!StrUtil.isEmpty(userName) && !StrUtil.isEmpty(password)) {
try {
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
SecurityUtils.getSubject().login(token);
HashMap<String, String> map = new HashMap(16);
map.put("access_token", "1111111111111111111");
return "index";
} catch (IncorrectCredentialsException var7) {
log.info("密码错误 {}", var7.getMessage());
} catch (UnknownAccountException var8) {
log.info("账号不存在 {}", var8.getMessage());
} catch (LockedAccountException var9) {
log.info("账号被锁定 {}", var9.getMessage());
} catch (ExcessiveAttemptsException var10) {
log.info("操作频繁,请稍后再试 {}", var10.getMessage());
} catch (Exception var11) {
log.error("登录异常 {}", var11.getMessage(), var11);
}
}
return "login";
}
至此 shiro 的系统已经完成。基于 shiro-spring-boot-web-starter 的项目就是如此简单。
下面增加分布式,在项目启动类上添加注解,注入注册中心,启用 redisHttpSession,会解析 cookie 的 sessionId 并将数据存放的 reids 中,以实现分布式 session.
@EnableRedisHttpSession
@EnableDiscoveryClient
@SpringBootApplication
将项目加入网关通过网关来访问,登录之后即可实现分布式 session。需要注意的是 session 的作用域,需要在相同的域名 domain 下才有效。
对于前后端分离的项目,登录后拿到 cookie, 每次请求 cookie 携带 sessionId 也可以实现 shiro 的分布式 session.
gitee 代码:https://gitee.com/tg_seahorse/paw-demos/tree/paw-authorize/
Rubble
还未添加个人签名 2021.06.01 加入
还未添加个人简介
评论