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

yizhihongxing

下面是关于“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日

相关文章

  • Java编程之文件读写实例详解

    《Java编程之文件读写实例详解》的攻略如下: 第一步:导入IO库 首先要在Java代码中导入IO库,这个库包括文件读写所需的各种类。 import java.io.*; 第二步:读取文件 使用BufferedReader类读取文本文件,具体方法如下: try{ BufferedReader reader = new BufferedReader(new F…

    Java 2023年5月20日
    00
  • 使用Spring Boot 2.x构建Web服务的详细代码

    下面就是针对使用Spring Boot 2.x构建Web服务的完整攻略: 1. 创建Spring Boot项目 首先,我们需要在IDE或者命令行中创建一个Spring Boot项目。使用IDE可以直接创建一个Spring Boot项目模板;使用命令行则需要使用Maven构建,具体做法如下: 首先,我们需要在本地装好Maven,然后在命令行中输入 mvn ar…

    Java 2023年5月19日
    00
  • Spring Boot 添加MySQL数据库及JPA实例

    下面是详细的“Spring Boot 添加MySQL数据库及JPA实例”的攻略。 1. 准备工作 安装Java和MySQL 新建Spring Boot项目(可使用IntelliJ IDEA等集成开发环境) 2. 添加MySQL依赖 在pom.xml文件中添加mysql-connector-java和spring-boot-starter-data-jpa依赖…

    Java 2023年5月20日
    00
  • springboot多环境配置方案(不用5分钟)

    下面是详细讲解“springboot多环境配置方案(不用5分钟)”的完整攻略: 1. 原理 Spring Boot 支持通过不同的配置文件来管理不同的环境。它提供了一个标准的命名规则:application-{profile}.properties/yml,比如 application-dev.yml,application-test.yml,applica…

    Java 2023年5月15日
    00
  • Java实例化类详解

    Java实例化类详解 在Java中,实例化类是创建类对象的过程。当我们创建一个类对象的时候,就可以使用该类所定义的方法和属性。 实例化类的基础知识 我们可以使用 new 关键字来创建一个类的实例,其基本语法如下: ClassName obj = new ClassName(); 其中,ClassName 是需要创建实例的类名,obj 是创建的对象名。在创建对…

    Java 2023年5月26日
    00
  • SpringBoot 创建web项目并部署到外部Tomcat

    下面是关于SpringBoot创建Web项目并部署到外部Tomcat的攻略。 1. 创建SpringBoot项目 首先,我们需要创建一个SpringBoot Web项目。在这里,我们可以使用Spring Initializr,它是一个基于Web的Spring Boot项目生成器,可以快速构建Spring Boot项目。 具体来说,可以按照以下步骤创建Spri…

    Java 2023年5月19日
    00
  • Struts2 OGNL表达式实例详解

    Struts2 OGNL表达式实例详解 1. 什么是OGNL OGNL即Object-Graph Navigation Language,是一个强大的表达式语言,它可以对Java对象进行操作并获取想要的数据,Struts2框架中使用OGNL表达式引擎来解析前端传递的参数,同时也支持在配置文件中使用OGNL表达式。 2. OGNL表达式语法 OGNL表达式的语…

    Java 2023年5月20日
    00
  • IDEA安装阿里巴巴编码规范插件的两种方式详解(在线安装和离线安装)

    下面是详细的攻略过程: 一、在线安装方式 打开IntelliJ IDEA编辑器,点击菜单栏中的「File」,选择下拉菜单中的「Settings」。 在弹出的设置页面中,找到「Plugins」选项,点击左侧的「Marketplace」,在搜索框输入「Alibaba」,点击搜索图标。 在搜索结果中会出现「Alibaba Java Coding Guideline…

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