Apache shiro的简单介绍与使用教程(与spring整合使用)

下面是关于“Apache Shiro的简单介绍与使用教程(与Spring整合使用)”的完整攻略。

什么是Apache Shiro

Apache Shiro是一个强大且易于使用的Java安全框架,它提供了身份验证、授权、密码加密等安全性功能。Shiro使用简单,易于扩展和集成到任何应用程序中,它的目标是成为Java世界最全面和最灵活的安全框架。

Shiro的核心组件

在了解如何使用Shiro之前,我们需要先了解Shiro的核心组件,这些组件是Shiro的主要概念和基础知识点:

  1. Subject:可以是任何与应用程序交互的主体,如用户、设备等。Subject可以是人(如一个登录用户),也可以是另一个应用程序或系统。

  2. SecurityManager:负责所有Subject的安全操作,包括认证、授权和加密。它是Shiro框架的核心组件。

  3. Realm:负责与安全相关的数据的访问,如从数据库获取用户信息。

下面我们来看一下如何使用Shiro。

快速开始

引入依赖

首先需要在代码中引入Shiro的依赖,在Maven项目中添加以下依赖:

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.7.1</version>
</dependency>

编写Shiro配置

然后需要在Spring配置文件中添加Shiro的配置,以下是一个简单的Shiro配置示例:

<!-- Shiro的配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  <!-- Realm配置 -->
  <property name="realm" ref="myRealm"/>
</bean>

<bean id="myRealm" class="com.example.MyRealm"/>

<!-- Shiro的过滤器链配置 -->
<bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  <property name="securityManager" ref="securityManager"/>
  <property name="loginUrl" value="/login"/>
  <property name="successUrl" value="/home"/>
  <property name="filterChainDefinitions">
    <value>
      /login = authc
      /** = anon
    </value>
  </property>
</bean>

在上面的配置中,我们配置了一个安全管理器(securityManager),一个Realm(myRealm),以及Shiro的过滤器链(ShiroFilterFactoryBean)。

编写自定义Realm

然后我们需要编写一个自定义Realm类,用于实现身份验证和授权逻辑。以下是一个简单的示例:

public class MyRealm extends AuthorizingRealm {

  @Autowired
  private UserService userService;

  /**
   * 认证方法
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    // 获取用户名和密码
    String username = (String) token.getPrincipal();
    String password = new String((char[]) token.getCredentials());

    // 从数据库中获取用户信息
    User user = userService.findByUsername(username);

    // 如果用户不存在,抛出异常
    if (user == null) {
      throw new UnknownAccountException("用户名或密码错误");
    }

    // 验证密码是否正确
    if (!user.getPassword().equals(password)) {
      throw new IncorrectCredentialsException("用户名或密码错误");
    }

    // 认证通过,返回一个AuthenticationInfo对象
    return new SimpleAuthenticationInfo(username, password, getName());
  }

  /**
   * 授权方法
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // 获取当前用户的用户名
    String username = (String) principals.getPrimaryPrincipal();

    // 从数据库中获取用户角色和权限信息
    Set<String> roles = userService.findRolesByUsername(username);
    Set<String> permissions = userService.findPermissionsByUsername(username);

    // 创建一个授权信息对象并返回
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.setRoles(roles);
    info.setStringPermissions(permissions);
    return info;
  }

}

在上面的示例中,我们继承了AuthorizingRealm类,并实现了doGetAuthenticationInfo和doGetAuthorizationInfo方法。doGetAuthenticationInfo方法用于身份验证,doGetAuthorizationInfo方法用于授权。

编写登录页面

接下来,我们需要编写一个简单的登录页面,让用户输入用户名和密码,然后进行身份验证。以下是一个简单的登录页面示例:

<!DOCTYPE html>
<html>
<head>
  <title>Shiro登录示例</title>
</head>
<body>
  <form id="loginForm" method="post" action="/login">
    <label>用户名:</label>
    <input type="text" name="username"/><br/>
    <label>密码:</label>
    <input type="password" name="password"/><br/>
    <input type="submit" value="登录"/>
  </form>
</body>
</html>

在上面的代码中,我们创建了一个表单,用户可以在这个表单中输入用户名和密码,然后提交到/login页面进行身份验证。

编写测试代码

最后,我们需要编写一个简单的测试类来验证我们的Shiro配置是否有效。以下是一个简单的测试类示例:

public class ShiroTest {

  private static final String USERNAME = "admin";
  private static final String PASSWORD = "123456";

  @Test
  public void testLogin() {
    // 创建一个SecurityManager实例
    DefaultSecurityManager securityManager = new DefaultSecurityManager();

    // 设置Realm
    securityManager.setRealm(new MyRealm());

    // 将SecurityManager绑定到SecurityUtils
    SecurityUtils.setSecurityManager(securityManager);

    // 获取Subject
    Subject subject = SecurityUtils.getSubject();

    // 构造一个登录令牌
    UsernamePasswordToken token = new UsernamePasswordToken(USERNAME, PASSWORD);

    // 尝试登录
    subject.login(token);

    // 验证用户是否成功登录
    assertTrue(subject.isAuthenticated());
  }

}

在上面的代码中,我们首先创建了一个SecurityManager实例,然后设置了我们自己编写的Realm类。最后,我们通过SecurityUtils.getSubject()方法获取了一个Subject实例,并使用login方法进行身份验证。

示例

以下是两个使用Shiro的示例:

示例1:登录验证

编写Controller

@Controller
public class LoginController {

  @GetMapping("/login")
  public String getLoginPage() {
    return "login";
  }

  @PostMapping("/login")
  public String doLogin(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        HttpServletRequest request) {
    // 获取Subject
    Subject subject = SecurityUtils.getSubject();

    // 构造登录令牌
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);

    try {
      // 尝试登录
      subject.login(token);

      // 登录成功,重定向到主页
      return "redirect:/home";
    } catch (UnknownAccountException | IncorrectCredentialsException e) {
      // 登录失败,跳转回登录页面并显示错误消息
      request.setAttribute("error", "用户名或密码错误");
      return "login";
    }
  }

  @GetMapping("/home")
  public String getHomePage() {
    return "home";
  }

}

在上面的Controller示例中,我们首先定义了一个getLoginPage方法和doLogin方法,用于展示登录页面和处理用户提交的登录表单。在doLogin方法中,我们首先使用SecurityUtils.getSubject()方法获取了一个Subject实例,然后构造了一个用户名/密码类型的登录令牌。最后,我们通过subject.login(token)方法进行身份验证。

编写登录页面

<!DOCTYPE html>
<html>
<head>
  <title>登录页面</title>
  <style type="text/css">
    .error {
      color: red;
    }
  </style>
</head>
<body>
  <form id="loginForm" method="post" action="/login">
    <label>用户名:</label>
    <input type="text" name="username"/><br/>
    <label>密码:</label>
    <input type="password" name="password"/><br/>
    <input type="submit" value="登录"/>
    <c:if test="${not empty error}">
      <div class="error">${error}</div>
    </c:if>
  </form>
</body>
</html>

在上面的代码中,我们创建了一个表单,用于输入用户名和密码,然后将这个表单通过post方法提交到/login页面进行身份验证。如果验证失败,我们会跳转回登录页面并显示错误消息。

编写测试用例

public class LoginControllerTest {

  @Autowired
  private LoginController loginController;

  @Autowired
  private MockMvc mvc;

  @Test
  public void testLoginPage() throws Exception {
    mvc.perform(get("/login"))
       .andExpect(status().isOk())
       .andExpect(view().name("login"))
       .andExpect(forwardedUrl("/WEB-INF/jsp/login.jsp"));
  }

  @Test
  public void testLoginSuccess() throws Exception {
    mvc.perform(post("/login").param("username", "admin").param("password", "123456"))
       .andExpect(status().is3xxRedirection())
       .andExpect(redirectedUrl("/home"));
  }

  @Test
  public void testLoginFailure() throws Exception {
    mvc.perform(post("/login").param("username", "admin").param("password", "wrong"))
       .andExpect(status().isOk())
       .andExpect(view().name("login"))
       .andExpect(forwardedUrl("/WEB-INF/jsp/login.jsp"))
       .andExpect(model().attributeExists("error"));
  }

}

在上面的测试用例中,我们分别测试了登录页面的测试、登录成功测试和登录失败测试。在登录成功测试和登录失败测试中,我们使用post方法并向/login页面提交用户名和密码,然后验证登录成功或失败的表现是否符合预期。

示例2:使用注解进行授权

编写Controller

@RestController
@RequestMapping("/api/user")
@RequiresRoles("admin")
public class UserController {

  @Autowired
  private UserService userService;

  @GetMapping("")
  public List<User> listUsers() {
    return userService.listUsers();
  }

  @GetMapping("/{id}")
  @RequiresPermissions("user:read")
  public User getUser(@PathVariable Long id) {
    return userService.getUser(id);
  }

  @PostMapping("")
  @RequiresPermissions("user:create")
  public User createUser(@RequestBody User user) {
    return userService.createUser(user);
  }

  @PutMapping("/{id}")
  @RequiresPermissions("user:update")
  public User updateUser(@PathVariable Long id, @RequestBody User user) {
    return userService.updateUser(id, user);
  }

  @DeleteMapping("/{id}")
  @RequiresPermissions("user:delete")
  public void deleteUser(@PathVariable Long id) {
    userService.deleteUser(id);
  }

}

在上面的示例中,我们定义了一个UserController类,并在类级别使用了RequiresRoles注解,表示该类中的所有方法需要admin角色才能访问。同时,我们还在getUser、createUser、updateUser和deleteUser方法中使用了RequiresPermissions注解,表示这些方法需要user:read、user:create、user:update和user:delete权限才能访问。

编写测试用例

public class UserControllerTest {

  @Autowired
  private WebApplicationContext context;

  private MockMvc mvc;

  private String token;

  @Before
  public void setup() throws Exception {
    mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();

    UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
    MockHttpSession session = new MockHttpSession();
    subject().login(token);
    SecurityContextHolder.getContext().setAuthentication(token);

    this.token = session.getId();
  }

  private Subject subject() {
    return SecurityUtils.getSubject();
  }

  private static String getCsrfToken(MockHttpSession session) throws Exception {
    ResultActions resultActions = mockMvc.perform(get("/api/token").session(session))
      .andExpect(status().isOk());
    MvcResult mvcResult = resultActions.andReturn();
    return mvcResult.getResponse().getContentAsString();
  }

  @Test
  public void testListUsers() throws Exception {
    mvc.perform(get("/api/user").header("X-CSRF-TOKEN", getCsrfToken()).session(new MockHttpSession()))
      .andExpect(status().isOk());
  }

  @Test
  public void testGetUser() throws Exception {
    mvc.perform(get("/api/user/1").header("X-CSRF-TOKEN", getCsrfToken()).session(new MockHttpSession()))
      .andExpect(status().isOk());
  }

  @Test
  public void testCreateUser() throws Exception {
    User newUser = new User("test", "test");
    mvc.perform(post("/api/user")
                  .contentType(MediaType.APPLICATION_JSON)
                  .content(new ObjectMapper().writeValueAsString(newUser))
                  .header("X-CSRF-TOKEN", getCsrfToken())
                  .session(new MockHttpSession()))
       .andExpect(status().isOk());
  }

  @Test
  public void testUpdateUser() throws Exception {
    User updateUser = new User("test", "test");
    mvc.perform(put("/api/user/1")
                  .contentType(MediaType.APPLICATION_JSON)
                  .content(new ObjectMapper().writeValueAsString(updateUser))
                  .header("X-CSRF-TOKEN", getCsrfToken())
                  .session(new MockHttpSession()))
       .andExpect(status().isOk());
  }

  @Test
  public void testDeleteUser() throws Exception {
    mvc.perform(delete("/api/user/1")
                  .header("X-CSRF-TOKEN", getCsrfToken())
                  .session(new MockHttpSession()))
       .andExpect(status().isOk());
  }

}

在上面的测试用例中,我们使用MockMvc模拟了Http请求,并在请求头中添加了X-CSRF-TOKEN头,用于防止跨站请求伪造。在测试各个方法时,我们使用了不同的Http请求方法和路径,并验证了返回状态是否为Ok。同时,我们已经实现了用户角色和权限的控制,确保只有拥有admin角色和对应权限的用户才能访问对应的方法。

结论

Shiro是一个强大而又易于使用的Java安全框架,它提供了身份验证、授权、密码加密等安全性功能,并为Java开发者提供了全面的、灵活的安全架构。本文通过编写示例代码介绍了Shiro的基本概念、特点以及如何使用Shiro实现身份验证和授权,同时给出了使用注解进行授权和测试Shiro代码的示例,希望能够为你使用Shiro提供帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Apache shiro的简单介绍与使用教程(与spring整合使用) - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Spring Boot构建系统安全层的步骤

    下面是Spring Boot构建系统安全层的步骤完整攻略及其两条示例说明。 步骤一:添加Spring Security依赖 首先,在pom.xml文件中添加Spring Security依赖。Spring Boot提供了许多预定义依赖项,其中包括Spring Security依赖项。可以在pom.xml中添加以下行来添加Spring Security依赖: …

    Java 2023年6月3日
    00
  • Eclipse配置Tomcat和JDK步骤图解

    下面是Eclipse配置Tomcat和JDK的详细攻略: 步骤一:下载和安装JDK并设置环境变量 前往Oracle官网下载JDK安装包并安装; 新建系统环境变量JAVA_HOME,值为JDK的安装路径; 在系统环境变量中,找到Path,添加%JAVA_HOME%\bin路径。 步骤二:下载Tomcat并在Eclipse中安装 前往Tomcat官网下载最新版本…

    Java 2023年5月19日
    00
  • CentOS7和8中安装Maven3.8.4的简单步骤

    下面我为你详细讲解 “CentOS7和8中安装Maven3.8.4的简单步骤”的完整攻略。 安装Java环境 在安装Maven之前,需要先在服务器上安装Java环境,否则Maven将无法正常使用。 # 在终端输入以下命令进行Java环境的安装 yum install java-1.8.0-openjdk-devel -y 安装完成后,检查Java环境是否正常…

    Java 2023年5月19日
    00
  • Java指令重排序在多线程环境下的处理方法

    Java指令重排序在多线程环境下的处理方法是非常重要的,因为指令重排序可能导致程序出现难以预测的结果,尤其是在多线程环境下。下面,我将详细讲解Java指令重排序在多线程环境下的处理方法,包括原理、处理方法和示例。 原理 Java指令重排序是指JVM在执行指令时,为了优化程序执行效率,可能会调整指令的执行顺序。这种优化不会影响单线程程序的执行,但是在多线程环境…

    Java 2023年5月26日
    00
  • java 抓取网页内容实现代码

    Java 抓取网页内容实现代码的完整攻略分为以下几个步骤: 建立与目标网站的网络连接。 Java 通过 URL 对象建立与目标网站的网络连接。URL 对象通过带参数的构造函数传入要访问的网站地址。 URL url = new URL("https://www.example.com"); 获取网络连接的输入流。 通过 URL 对象的 op…

    Java 2023年5月23日
    00
  • Java中的并发是什么?

    Java中的并发是指多个线程同时执行的状态。简单来说,就是在同一时刻有多个线程在运行,并且这些线程可以共享相同的资源。Java中提供了一些方便且有效的机制来处理并发并保障线程安全。 Java中的线程 Java中的线程是由Thread类实例化的对象,通过start()方法启动。Java中的线程可以分为两种类型,分别为用户线程和守护线程。用户线程运行结束后,程序…

    Java 2023年4月27日
    00
  • 浅谈java定时器的发展历程

    浅谈Java定时器的发展历程 什么是定时器 定时器是一种在预设时间内周期性地执行任务的机制,通常用于定期执行一些任务,或者实现某些重复性的操作。在Java中,定时器一般是基于Timer类和ScheduledExecutorService实现的。 Java定时器的发展历程 Timer 在Java最早的版本中,Timer是实现定时器功能的主要类。它可以通过sch…

    Java 2023年5月26日
    00
  • Spring 校验(validator,JSR-303)简单实现方式

    实现一个完整的表单校验是 Web 应用中非常重要的组成部分。Spring 框架提供了校验的功能,它支持 JSR-303 规范和 Spring Validator 接口两种校验方式。本文将为大家介绍 Spring 校验的简单实现方式。 JSR-303 校验方式 下面将演示一个基于 JSR-303 规范实现的表单校验示例。 引入依赖 首先需要引入 Spring …

    Java 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部