Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。Shiro首要的和最重要的目标就是容易使用并且容易理解,通过Shiro易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序最大的网络和企业应用程序。
- Authentication:身份认证/登录
- Authorization:验证权限,即,验证某个人是否有做某件事的权限。
- Session Management:会话管理。管理用户特定的会话,支持web,非web,ejb。
- Cryptography: 加密,保证数据安全。
- Web Support:web支持,更容易继承web应用。
- Caching:缓存
- Concurrency :多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持。
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,即记住登录状态,一次登录后,下次再来的话不用登录了
Shiro的架构有三个主要概念:Subject, SecurityManager 和 Realms。
org.apache.shiro
shiro-spring
1.4.0
Realm是shiro进行登录或者权限校验的关键位置,我们需要重写里面的方法,分别是:
/**
* @Author: oyc
* @Date: 2020-06-02 16:12
* @Description:
*/
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//根据用户名去数据库查询用户信息,此处模拟
User user = new User(1,name,"123456");
// 添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加角色
simpleAuthorizationInfo.addRole(user.getAccount());
//添加权限,admin:add/user:add
simpleAuthorizationInfo.addStringPermission(user.getAccount()+":add");
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (authenticationToken.getPrincipal() == null) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
// 根据用户名获取用户信息,此处模拟
User user = new User(1,name,"123456");
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
return new SimpleAuthenticationInfo(name, user.getPassword(), getName());
}
}
}
/**
* @Author: oyc
* @Date: 2020-06-02 16:11
* @Description: Shiro 配置
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器
Map filterChainDefinitionMap = new LinkedHashMap<>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "logout");
//过滤链定义,从上向下顺序执行,一般将/**放在最为下边
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
//未登录,重定向到登录页面,如果是前后端分离,不需要
shiroFilterFactoryBean.setLoginUrl("/login");
return shiroFilterFactoryBean;
}
/**
* 将自己的验证方式加入容器
*/
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(CustomRealm myShiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setRealm(myShiroRealm);
return securityManager;
}
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@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(myShiroRealm()));
return authorizationAttributeSourceAdvisor;
}
}
/**
* @Author: oyc
* @Date: 2020-06-02 16:41
* @Description: 全局异常处理
*/
@RestControllerAdvice
@Slf4j
public class ExceptionController {
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(AuthorizationException.class)
public ResponseEntity ErrorHandler401(AuthorizationException e) {
log.error("没有通过权限验证!", e);
Map result = new HashMap();
result.put("status", "401");
//获取错误中中括号的内容
String message = e.getMessage();
String msg=message.substring(message.indexOf("[")+1,message.indexOf("]"));
//判断是角色错误还是权限错误
if (message.contains("role")) {
result.put("msg", "对不起,您没有" + msg + "角色");
} else if (message.contains("permission")) {
result.put("msg", "对不起,您没有" + msg + "权限");
} else {
result.put("msg", "对不起,您的权限有误");
}
return new ResponseEntity(result, HttpStatus.FORBIDDEN);
}
/**
* shiro的异常
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(ShiroException.class)
public ResponseEntity handleShiroException(ShiroException e) {
log.error("系统发生异常!", e);
return new ResponseEntity(e.getMessage(), HttpStatus.UNAUTHORIZED);
}
/**
* 捕捉其他所有异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity globalException(HttpServletRequest request, Throwable e) {
log.error("系统发生异常!", e);
return new ResponseEntity(e.getMessage(), HttpStatus.valueOf(getStatus(request).value()));
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
链接不存在异常处理:
/**
* @Description: 404 异常处理
* @Author oyc
* @Date 2020/6/6 12:14 上午
*/
@RestController
public class NotFoundException implements ErrorController {
private static final String ERROR_PATH = "/error";
@Override
public String getErrorPath() {
return ERROR_PATH;
}
@RequestMapping(ERROR_PATH)
public ResponseEntity error(){
return ResponseEntity.badRequest().body("接口不存在!");
}
}
/**
* @Author: oyc
* @Date: 2020-06-02 16:39
* @Description: 登录控制类
*/
@RestController
public class LoginController {
@GetMapping("/login")
public String login(User user) {
//添加用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getAccount(), user.getPassword());
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
} catch (AuthenticationException e) {
e.printStackTrace();
return "账号或密码错误!";
} catch (AuthorizationException e) {
e.printStackTrace();
return "没有权限!";
}
return "用户["+SecurityUtils.getSubject().getPrincipal()+"]登录成功";
}
/**
* 注解检验角色-admin
*/
@RequiresRoles("admin")
@RequestMapping("role/admin")
public String roleAdmin() {
return "当前登录用户拥有admin角色";
}
/**
* 注解检验角色-user
*/
@RequiresRoles("user")
@RequestMapping("role/user")
public String roleUser() {
return "当前登录用户拥有user角色";
}
/**
* 注解检验权限 -- admin:add
*/
@RequiresPermissions("admin:add")
@RequestMapping("perm/adminAdd")
public String userAdd() {
return "当前登录用户拥有user:add权限";
}
/**
* 注解检验权限 -- user:add
*/
@RequiresPermissions("user:add")
@RequestMapping("perm/userAdd")
public String userView() {
return "当前登录用户拥有user:add权限";
}
/**
* 注解检验权限 --user user:add
*/
@RequiresRoles("user")
@RequiresPermissions("user:add")
@RequestMapping("role/perm/userAdd")
public String rolePermserAdd() {
return "当前登录用户拥有admin角色和user:add权限";
}
}
用户登录admin
role admin权限
role user角色
user:add 权限
参考源码:https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-shiro