SpringBoot整合Shiro–身份认证和权限管理
一、Shiro是什么?
Shiro 是 Java 的一个安全框架
。因为它使用简单,对比 Spring Security,可能没有 Spring Security 的功能强大,但是在很多情况下可能并不需要那么复杂的东西,所以使用Shiro就能够满足日常开发需要 。
Shiro 的核心功能组成如图:
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用在输入账号密码即可完成登录。
shiro实现原理:
简单理解就是:
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager; 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
二、SpringBoot整合使用
- pom 添加依赖
<!-- shiro与spring整合依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.1</version>
</dependency>
- ShiroConfig 添加配置类
/**
* Shiro的配置类
*
* @author jz
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* roles: 该资源必须得到角色权限才可以访问
*/
Map<String, String> filterMap = new LinkedHashMap<String, String>();
//下一级覆盖上一级 支持通配符*
filterMap.put("/", "anon");
filterMap.put("/index", "anon");
filterMap.put("/toLogin", "anon");
filterMap.put("/toRegist", "anon");
filterMap.put("/dream", "anon");
filterMap.put("/allproject", "anon");
filterMap.put("/publishProject*", "authc");
//授权过滤器
//注意:当前授权拦截后,shiro会自动跳转到未授权页面
//角色权限授权
filterMap.put("/admin/**", "roles[1]");
filterMap.put("/admin", "roles[1]");
//资源权限认证
//filterMap.put("/update", "perms[user:update]");
//filterMap.put("/delete", "authc,perms[user:delete]");
//修改调整的登录页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//修改未授权的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//修改成功页面
//shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getRealm() {
UserRealm userRealm = new UserRealm();
//加密算法 对比密码和数据库密码必须为加密密码
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("Md5"); //加密算法
hashedCredentialsMatcher.setHashIterations(11); //加密次数
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
}
- UserRealm 添加Realm类
package jz.shiro;
/**
* 自定义Realm
*
* @author jz
*/
public class UserRealm extends AuthorizingRealm {
//执行查询数据库的业务类
@Autowired(required = false)
UserService userService;
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("执行授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加资源的授权字符串
//info.addStringPermission("user:add");
//到数据库查询当前登录用户的授权字符串
//获取当前登录用户
Subject subject = SecurityUtils.getSubject();
UserInfo user = (UserInfo) subject.getPrincipal();//得到当前用户
String roleName = null;
System.out.println(user);
//通过权限对象的id来验证身份角色
if (user != null) {
UserRole userRole = new UserRole().setUserInfo(new UserInfo().setId(user.getId()));
List<UserRole> userRoles = userService.findUserRole(userRole, null);
if (userRoles != null) {
if (userRoles.size() > 0) {
roleName = String.valueOf(userRoles.get(0).getRole().getId());
}
}
}
info.addRole(roleName);
return info;
}
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//编写shiro判断逻辑,判断用户名和密码
System.out.println("执行认证逻辑");
//1.获取用户名 --依次执行查询 用户名、邮箱、电话
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
List<UserInfo> users = userService.findUserInfo(
new UserInfo().setUUsername(token.getUsername()).setStatus("1"), null);
if (users == null || users.size() <= 0) {
users = userService.findUserInfo(
new UserInfo().setUEmail(token.getUsername()).setStatus("1"), null);
}
if (users == null || users.size() <= 0) {
users = userService.findUserInfo(
new UserInfo().setUPhone(token.getUsername()).setStatus("1"), null);
}
//判断是否用户存在
if (users == null || users.size() <= 0) {
throw new UnknownAccountException("该账号不存在!");
}
//获取匹配的唯一用户
UserInfo user = users.get(0);
//判断是否用户已被锁定
if ("1".equals(user.getULock())) {
throw new AuthenticationException("该账号已被锁定!");
}
//shiro 自带加密算法
//SimpleHash password = new SimpleHash("MD5", user.getUPassword(), token.getUsername(),11);
//加密
//System.out.println("加密后密码:"+password.toString());
//用用户邮箱加盐
ByteSource salt = ByteSource.Util.bytes(user.getUEmail());
//2.判断密码
return new SimpleAuthenticationInfo(user, user.getUPassword(), salt, "");
}
}
- controller 控制
package jz.controller;
/**
* author: jz
* Time: 2020/4/17 21:35
**/
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
UserService userService;
/*--------登录注册模块-------------*/
/*登录*/
@GetMapping("/login")
public MyResponse login(UserInfo userInfo, HttpServletRequest request, String rememberMe) {
MyResponse myResponse = new MyResponse("500", "登录发生错误");
System.out.println(userInfo);
System.out.println(rememberMe);
//登录判断控制
if (userInfo != null) {
if (!StrUtil.hasEmpty(userInfo.getUUsername(), userInfo.getUPassword())) {
//把前台获取的数据放入Shiro的UsernamePasswordToken中
UsernamePasswordToken token = new UsernamePasswordToken(
userInfo.getUUsername(), userInfo.getUPassword());
//获取当前登录的对象
Subject currentUser = SecurityUtils.getSubject();
//保留拦截前的地址
String rUrl = null;
if (currentUser.getSession(false) != null) {
SavedRequest reqUrl = WebUtils.getSavedRequest(request);
if (reqUrl != null) {
rUrl = reqUrl.getRequestUrl();
}
}
//是否记住我
if ("on".equals(rememberMe)) {
token.setRememberMe(true);
}
//登录判断
try {
//主体提交登录请求到SecurityManager
currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
myResponse.setMsg("该账号或密码不正确");
} catch (UnknownAccountException uae) {
myResponse.setMsg("该账号不存在");
} catch (AuthenticationException ae) {
myResponse.setMsg("该账号已被锁定");
}
//登录成功
if (currentUser.isAuthenticated() || currentUser.isRemembered()) {
System.out.println("认证成功");
HashMap<String, Object> map = new LinkedHashMap<>();
map.put("userInfo", (UserInfo) currentUser.getPrincipal());
map.put("rUrl", rUrl);
myResponse.setCode("200").setMsg("认证成功").setData(map);
System.out.println((UserInfo) currentUser.getPrincipal());
request.getSession().setAttribute("nowUser", (UserInfo) currentUser.getPrincipal());
} else {
token.clear();
}
}
}
return myResponse;
}
}
- 省略其他
省略了数据库查询绑定的Dao、Mapper、业务层Sevice、用户对象User、以及前端页面等。
毕竟重点不在于此,故省略之。
三、结束
好了,整合完毕,是不是很简单,只需要去测试下是否预计要拦截的页面是否已拦截并调到登录界面了。
原理就是把认证和授权交给Shiro来处理,我们无需关注Shiro是如何认证的,我们只需要提供要验证的用户信息和数据库的信息即可。