使用springboot集成shiro框架
1.shiro是什么?
Shiro是Apache下的一个开源项目。shiro属于轻量级框架,相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂
官方架构图如下:
2.shiro主要有三大功能模块:
- Subject:主体,一般指用户。
- SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)
- Realms:用于进行权限信息的验证,一般需要自己实现。
3.细分功能
5. Authentication:身份认证/登录(账号密码验证)。
6. Authorization:授权,即角色或者权限验证。
7. Session Manager:会话管理,用户登录后的session相关管理。
8. Cryptography:加密,密码加密等。
9. Web Support:Web支持,集成Web环境。
10. Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。
11. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
12. Testing:测试支持;
13. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
14. Remember Me:记住我,登录后,下次再来的话不用登录了。
上代码:
目录结构:
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
UserEntity实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
@Data
@AllArgsConstructor
public class UserEntity {
private String id;
private String userName;
private String password;
/**
* 用户对应的角色集合
*/
private Set<RoleEntity> roles;
}
RoleEntity实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
@Data
@AllArgsConstructor
public class RoleEntity {
private String id;
private String roleName;
/**
* 角色对应权限集合
*/
private Set<PrimissionsEntity> permissions;
}
PrimissionsEntity实体类
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class PrimissionsEntity {
private String id;
private String primissionsName;
}
LoginServiceImpl.java中自己模拟的(RBAC)权限设计模式
import com.psq.springboot_shiro.entity.PrimissionsEntity;
import com.psq.springboot_shiro.entity.RoleEntity;
import com.psq.springboot_shiro.entity.UserEntity;
import com.psq.springboot_shiro.service.LoginService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Service
public class LoginServiceImpl implements LoginService {
@Override
public UserEntity getUserByName(String getMapByName) {
return getMapByName(getMapByName);
}
/**
* 模拟数据库查询
*
* @param userName 用户名
* @return UserEntity
*/
private UserEntity getMapByName(String userName) {
//声明权限实体类容器用于存放两个不重复的权限
Set<PrimissionsEntity> permissionsSet = new HashSet<>();
PrimissionsEntity primissionsEntity = new PrimissionsEntity("1", "query");
permissionsSet.add(primissionsEntity);
primissionsEntity = new PrimissionsEntity("2", "add");
permissionsSet.add(primissionsEntity);
//声明角色实体类容器用于存放不重复的角色
Set<RoleEntity> roleSet = new HashSet<>();
//创建一个角色,并将上面声明的权限绑定到这个角色中
RoleEntity roleEntity = new RoleEntity("1", "admin", permissionsSet);//管理员角色
roleSet.add(roleEntity);
//创建一个用户,并将上面声明的角色绑定到这个用户中
UserEntity userEntity = new UserEntity("1", "psq", "123456", roleSet);
Map<String, UserEntity> map = new HashMap<>();
//只要用户名称是psq,即角色是admin拥有的权限是query、add
map.put(userEntity.getUserName(), userEntity);
//再次创建一个query的权限
primissionsEntity = new PrimissionsEntity("3", "query");
permissionsSet = new HashSet<>();
permissionsSet.add(primissionsEntity);
//创建一个角色,并将上面声明的权限绑定到这个角色中
roleEntity = new RoleEntity("2", "user", permissionsSet);//用户角色
roleSet = new HashSet<>();
roleSet.add(roleEntity);
//只要用户名称是zhangsan,即角色是user拥有的权限是query
userEntity = new UserEntity("2", "zhangsan", "123456", roleSet);
map.put(userEntity.getUserName(), userEntity);
return map.get(userName);
}
}
自定义Realm用于查询用户的角色和权限信息并保存到权限管理器:
CustomRealm.java
import com.psq.springboot_shiro.entity.PrimissionsEntity;
import com.psq.springboot_shiro.entity.RoleEntity;
import com.psq.springboot_shiro.entity.UserEntity;
import com.psq.springboot_shiro.service.LoginService;
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;//shiro授权领域
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
/**
* shiro自定义权限
*/
public class CustomRealm extends AuthorizingRealm {
@Resource
private LoginService loginService;
/**
* @MethodName doGetAuthorizationInfo
* @Description 权限配置类
* @Param [principalCollection]
* @Return AuthorizationInfo //授权信息
* @Author psq/old_author by WangShiLin
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//查询用户名称
UserEntity userEntity = loginService.getUserByName(name);
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (RoleEntity role : userEntity.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//添加权限
for (PrimissionsEntity primissionsEntity : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(primissionsEntity.getPrimissionsName());
}
}
return simpleAuthorizationInfo;
}
/**
* @MethodName doGetAuthenticationInfo
* @Description 认证配置类
* @Param [authenticationToken]
* @Return AuthenticationInfo //认证信息
* @Author psq/old_author by WangShiLin
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
UserEntity userEntity = loginService.getUserByName(name);
if (userEntity == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, userEntity.getPassword(), getName());
return simpleAuthenticationInfo;
}
}
}
把CustomRealm和SecurityManager等注入到spring容器中:
ShiroConfig.java:
import org.apache.shiro.mgt.SecurityManager;
import com.psq.springboot_shiro.shiro.CustomRealm;
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//将自己的验证方式加入容器
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
我们编写一个简单的登录方法,一个index页的查询方法,一个add方法,一个admin方法,对应不同的角色或权限拦截
LoginController.java:
import com.psq.springboot_shiro.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class LoginController {
@GetMapping("/login")
public String login(UserEntity user) {
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return "请输入用户名和密码!";
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword()
);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
// subject.checkRole("admin");
// subject.checkPermissions("query", "add");
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "没有权限";
}
return "login success";
}
@RequiresRoles("admin")//拥有admin角色才能访问此接口
@GetMapping("/admin")
public String admin() {
return "admin success!";
}
@RequiresPermissions("query")//拥有query权限才能访问此接口
@GetMapping("/index")
public String index() {
return "index success!";
}
@RequiresPermissions("add")//拥有add权限才能访问此接口
@GetMapping("/add")
public String add() {
return "add success!";
}
}
注解验证角色和权限的话无法捕捉异常,从而无法正确的返回给前端错误信息,所以我加了一个类用于拦截异常,具体代码如下
MyExceptionHandler.java
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
log.error("没有通过权限验证!", e);
return "没有通过权限验证!";
}
}
打开网页 http://localhost:8080/login?userName=zhangsan&password=123456
再次打开一个页面输入:
http://localhost:8080/add
之所以没有权限是因为我们在serviceImpl模拟授权的时候没有给zhangsan授予add权限
换个psq用户
http://localhost:8080/login?userName=psq&password=123456
再次打开一个页面输入:
http://localhost:8080/add
此时就有权限访问了
本文参考博客 https://www.jianshu.com/p/7f724bec3dc3
按着上面步骤一步一步操作不会报错哦