目录

  • 一、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方法就不会被执行。

java实现api权限控制 java权限管理shiro_ci

@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测试登录接口

使用错误账号密码登录,返回账号不存在

java实现api权限控制 java权限管理shiro_spring boot_02


正确账号密码登录,返回登录成功

java实现api权限控制 java权限管理shiro_java_03

4.3测试test接口

测试用户没有权限的接口test,返回用户没授权信息,账号没对应的权限

java实现api权限控制 java权限管理shiro_java_04

4.4测试authority接口

测试用户具有权限的接口,返回接口信息

java实现api权限控制 java权限管理shiro_java_05

4.5测试current接口

测试获取当前账号信息接口,返回账号name

java实现api权限控制 java权限管理shiro_java实现api权限控制_06

4.6测试success接口

测试不需要权限的接口,返回成功

java实现api权限控制 java权限管理shiro_java_07

4.7测试退出登录接口

账号退出成功

java实现api权限控制 java权限管理shiro_ci_08

4.8测试current接口

账号退出后,校验接口是否还能访问,当前账号信息接口返回失败信息

java实现api权限控制 java权限管理shiro_ci_09

4.9测试success接口

账号退出后,校验公用的接口是否还能访问,接口返回失败信息

java实现api权限控制 java权限管理shiro_spring boot_10

五、demo下载

shiro项目下载