下面是关于“Apache Shiro的简单介绍与使用教程(与Spring整合使用)”的完整攻略。
什么是Apache Shiro
Apache Shiro是一个强大且易于使用的Java安全框架,它提供了身份验证、授权、密码加密等安全性功能。Shiro使用简单,易于扩展和集成到任何应用程序中,它的目标是成为Java世界最全面和最灵活的安全框架。
Shiro的核心组件
在了解如何使用Shiro之前,我们需要先了解Shiro的核心组件,这些组件是Shiro的主要概念和基础知识点:
-
Subject:可以是任何与应用程序交互的主体,如用户、设备等。Subject可以是人(如一个登录用户),也可以是另一个应用程序或系统。
-
SecurityManager:负责所有Subject的安全操作,包括认证、授权和加密。它是Shiro框架的核心组件。
-
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技术站