目录
- 一、Shiro是什么?
- 二、Shiro能做什么?
- 三、项目demo
- 3.1 项目依赖
- 3.2 配置文件config
- 3.3 自定义Realm
- 3.4 拦截器filter
- 3.5 控制器Controller
- 3.6 异常处理器Exception
- 四、测试
- 五、demo下载
一、Shiro是什么?
Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与 Spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式,是一个轻量级的安全管理框架
二、Shiro能做什么?
(1)身份认证/登录:验证用户是不是拥有相应的身份;
(2)授权:即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
(3)会话管理:即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
(4)加密:保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
(5)Web支持:可以非常容易的集成到Web环境;Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
(6)并发验证:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
(7)提供测试支持;
(8)允许切换用户:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
(9)单点登录:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Shiro 致力在所有应用环境下实现上述功能,小到命令行应用程序,大到企业应用中,而且不需要借助第三方框架、容器、应用服务器等。当然 Shiro 的目的是尽量的融入到这样的应用环境中去,但也可以在它们之外的任何环境下开箱即用。
Apache Shiro的官方文档:
http://shiro.apache.org/authentication.html
三、项目demo
这个demo做了精简整合,去掉一些重复不需要的代码,这个模块可以直接嵌入你的springboot项目系统中。
3.1 项目依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 单元测试支持 可以不依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.1</version>
</dependency>
<!-- log日志依赖 可以不依赖 需要自己写log配置-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
此处使用的开发软件是idea的,需要安装lombok插件,不然log日志会提示错误,但运行并不报错
3.2 配置文件config
ShiroFilterFactoryBean
:是个拦截器,在请求进入控制层前将其拦截,需要将安全管理器SecurityManager注入其中
SecurityManager
:安全管理器,需要将自定义realm注入其中,以后还可以将缓存、remeberme等注入其中
@Slf4j
@Configuration
public class shiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
log.info("----shiroFilterFactoryBean启动----");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.getFilters().put("authc", new ShiroFilter());
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/controller/login", "anon");
filterChainDefinitionMap.put("/controller/logout", "anon");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/controller/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
return defaultSecurityManager;
}
@Bean
public ShiroRealm customRealm() {
return new ShiroRealm();
}
@Bean
LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator result = new DefaultAdvisorAutoProxyCreator();
result.setProxyTargetClass(true);
return result;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*/
@Bean
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor result = new AuthorizationAttributeSourceAdvisor();
result.setSecurityManager(securityManager);
return result;
}
}
3.3 自定义Realm
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {
// 此demo不连接数据库,代码写死一个账号
private static final String name = "admin";
private static final String password = "123456";
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("---------用户权限授权-------------");
String username = (String) SecurityUtils.getSubject().getPrincipal();
// 连接数据库查询对应的账号权限,传入SimpleAuthenticationInfo中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> stringSet = new HashSet<>();
stringSet.add("authority");
info.setStringPermissions(stringSet);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("-------身份认证方法--------");
String userName = (String) authenticationToken.getPrincipal();
// 连接数据库查询对应账号密码,传入SimpleAuthenticationInfo中
return new SimpleAuthenticationInfo(name, password, getName());
}
}
3.4 拦截器filter
isAccessAllowed
:判断是否登录
在登录的情况下此方法返回true直接访问控制器onAccessDenied
:是否是拒绝登录
没有登录的情况下会走此方法
在使用Shiro框架的时候所有的请求经过过滤器都会来到此onPreHandle方法
如果isAccessAllowed方法返回true,则不会再调用onAccessDenied方法,如果isAccessAllowed方法返回Flase,则会继续调用onAccessDenied方法。
而isAccessAllowed方法里面则是具体执行登陆的地方。由于我们已经登陆,所以此方法就会返回True(filter放行),所以上面的onPreHandle方法里面的onAccessDenied方法就不会被执行。
@Slf4j
public class ShiroFilter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
log.info("----------判断是否已登录----------");
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws java.lang.Exception {
log.info("----------未登录!!!----------");
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.getWriter().write("fail");
return false;
}
}
3.5 控制器Controller
@Slf4j
@RestController
@RequestMapping("/controller")
public class controller {
@RequestMapping("/login")
public String login(String username, String password) {
log.info("name:" + username);
log.info("pwd:" + password);
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return ae.getMessage();
}
if (subject.isAuthenticated()) {
return "登录成功";
} else {
token.clear();
return "登录失败";
}
}
@RequestMapping("/current")
public String current() {
String code = (String) SecurityUtils.getSubject().getPrincipal();
System.out.println(code);
return code;
}
@RequestMapping("/test")
@RequiresPermissions("test")
public String test() {
return "test";
}
@RequestMapping("/authority")
@RequiresPermissions("authority")
public String authority() {
return "authority";
}
@RequestMapping("/success")
public String success() {
System.out.println("success方法");
return "成功";
}
@RequestMapping("/logout")
public String logout() {
System.out.println("logout");
SecurityUtils.getSubject().logout();
return "退出成功";
}
}
3.6 异常处理器Exception
@Slf4j
@ControllerAdvice
public class Exception {
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
public void authorizationError(HttpServletRequest request, HttpServletResponse response, AuthorizationException e) throws IOException {
String requestURI = request.getRequestURI();
log.info("授权异常请求:[{}],账号:[{}]", requestURI, SecurityUtils.getSubject().getPrincipal());
Map<String, Object> map = new HashMap<>();
map.put("status", "500");
map.put("message", "授权异常");
writeJson(map, response);
}
private void writeJson(Map<String, Object> map, HttpServletResponse response) {
PrintWriter out = null;
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
out = response.getWriter();
out.write(new JSONObject(map).toJSONString());
} catch (IOException e) {
} finally {
if (out != null) {
out.close();
}
}
}
}
四、测试
4.1启动项目
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.8.RELEASE)
2021-05-30 12:22:59.572 INFO 13272 --- [ main] com.xiao.shiro.ShiroApplication : Starting ShiroApplication on DESKTOP-VNONCKV with PID 13272 (D:\workpace\shiro\target\classes started by 小小 in D:\workpace\shiro)
2021-05-30 12:22:59.572 INFO 13272 --- [ main] com.xiao.shiro.ShiroApplication : No active profile set, falling back to default profiles: default
2021-05-30 12:23:01.662 INFO 13272 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'shiroConfig' of type [com.xiao.shiro.config.shiroConfig$$EnhancerBySpringCGLIB$$b778982d] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-05-30 12:23:04.152 INFO 13272 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'customRealm' of type [com.xiao.shiro.config.ShiroRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-05-30 12:23:04.162 INFO 13272 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'securityManager' of type [org.apache.shiro.web.mgt.DefaultWebSecurityManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-05-30 12:23:04.172 INFO 13272 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'authorizationAttributeSourceAdvisor' of type [org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-05-30 12:23:04.182 INFO 13272 --- [ main] com.xiao.shiro.config.shiroConfig : ----shiroFilterFactoryBean启动----
2021-05-30 12:23:04.462 INFO 13272 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-05-30 12:23:04.472 INFO 13272 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-05-30 12:23:04.472 INFO 13272 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-05-30 12:23:04.592 INFO 13272 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-05-30 12:23:04.592 INFO 13272 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4880 ms
2021-05-30 12:23:05.062 INFO 13272 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-05-30 12:23:05.432 INFO 13272 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-05-30 12:23:05.452 INFO 13272 --- [ main] com.xiao.shiro.ShiroApplication : Started ShiroApplication in 6.837 seconds (JVM running for 9.69)
启动成功,并无报错信息
4.2测试登录接口
使用错误账号密码登录,返回账号不存在
正确账号密码登录,返回登录成功
4.3测试test接口
测试用户没有权限的接口test,返回用户没授权信息,账号没对应的权限
4.4测试authority接口
测试用户具有权限的接口,返回接口信息
4.5测试current接口
测试获取当前账号信息接口,返回账号name
4.6测试success接口
测试不需要权限的接口,返回成功
4.7测试退出登录接口
账号退出成功
4.8测试current接口
账号退出后,校验接口是否还能访问,当前账号信息接口返回失败信息
4.9测试success接口
账号退出后,校验公用的接口是否还能访问,接口返回失败信息
五、demo下载