userName 3 nedēļas atpakaļ
vecāks
revīzija
e65f95a2ec

+ 15 - 0
pom.xml

@@ -16,9 +16,24 @@
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring-boot-web-starter</artifactId>
+            <version>1.9.1</version>
+        </dependency>
+        <dependency>
             <groupId>com.mysql</groupId>
             <artifactId>mysql-connector-j</artifactId>
         </dependency>

+ 31 - 0
src/main/java/com/zhentao/controller/LoginController.java

@@ -0,0 +1,31 @@
+package com.zhentao.controller;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.IncorrectCredentialsException;
+import org.apache.shiro.authc.UnknownAccountException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.subject.Subject;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+public class LoginController {
+
+    @PostMapping("/login")
+    public String login(@RequestParam String username, @RequestParam String password) {
+        Subject currentUser = SecurityUtils.getSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
+        try {
+            currentUser.login(token);
+            return "redirect:/home";
+        } catch (UnknownAccountException e) { // 用户名不存在
+            return "redirect:/login?error=invalid";
+        } catch (IncorrectCredentialsException e) { // 密码错误
+            return "redirect:/login?error=invalid";
+        } catch (AuthenticationException e) { // 其他认证异常(如账户锁定)
+            return "redirect:/login?error=unknown";
+        }
+    }
+}

+ 36 - 0
src/main/java/com/zhentao/shiro/config/MyRealm.java

@@ -0,0 +1,36 @@
+package com.zhentao.shiro.config;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MyRealm extends AuthorizingRealm {
+
+    // 授权方法
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
+        // 这里可以根据用户信息添加角色和权限
+        authorizationInfo.addRole("admin");
+        authorizationInfo.addStringPermission("user:manage");
+        return authorizationInfo;
+    }
+
+    // 认证方法
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+        String username = upToken.getUsername();
+        String password = "123456"; // 模拟从数据库获取的密码
+
+        if (!"admin".equals(username)) {
+            throw new UnknownAccountException("用户名不存在");
+        }
+
+        return new SimpleAuthenticationInfo(username, password, getName());
+    }
+}

+ 76 - 0
src/main/java/com/zhentao/shiro/config/ShiroConfig.java

@@ -0,0 +1,76 @@
+package com.zhentao.shiro.config;
+
+import com.zhentao.shiro.config.MyRealm;
+import org.apache.shiro.authc.Authenticator;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.authz.Authorizer;
+import org.apache.shiro.authz.ModularRealmAuthorizer;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
+import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
+import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
+import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+
+@Configuration
+public class ShiroConfig {
+
+    @Bean
+    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager mySecurityManager) {
+        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
+        shiroFilterFactoryBean.setSecurityManager(mySecurityManager);
+        // 登录页面
+        shiroFilterFactoryBean.setLoginUrl("/login");
+        // 未授权页面
+        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
+        return shiroFilterFactoryBean;
+    }
+
+    @Bean
+    public SecurityManager mySecurityManager(Authenticator authenticator, Authorizer authorizer, SessionManager sessionManager) {
+        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
+        securityManager.setAuthenticator(authenticator); // 使用配置好的 Authenticator
+        securityManager.setAuthorizer(authorizer);
+        securityManager.setSessionManager(sessionManager);
+        return securityManager;
+    }
+
+    @Bean
+    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
+        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
+        // 配置哪些请求需要受保护,以及访问这些请求需要的权限
+        chainDefinition.addPathDefinition("/login", "anon"); // 登录接口可匿名访问
+        chainDefinition.addPathDefinition("/unauthorized", "anon"); // 未授权页面可匿名访问
+        chainDefinition.addPathDefinition("/**", "authc"); // 其他请求需要认证
+        return chainDefinition;
+    }
+
+    @Bean
+    public Authenticator authenticator(MyRealm myRealm) { // 注入 MyRealm
+        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
+        // 将 MyRealm 添加到 Authenticator 的 realms 列表中
+        authenticator.setRealms(Collections.singletonList(myRealm));
+        return authenticator;
+    }
+
+    @Bean
+    public Authorizer authorizer() {
+        return new ModularRealmAuthorizer();
+    }
+
+    @Bean
+    public SessionManager sessionManager() {
+        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
+        // 这里可以进行更多的 sessionManager 配置,例如设置 sessionDAO
+        SessionDAO sessionDAO = new MemorySessionDAO();
+        sessionManager.setSessionDAO(sessionDAO);
+        return sessionManager;
+    }
+}

+ 6 - 0
src/main/resources/application.yml

@@ -1,2 +1,8 @@
 server:
   port: 9527
+spring:
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://127.0.0.1:3306/goose?serverTimezone=UTC
+    username: root
+    password: hch030923

+ 10 - 0
src/main/resources/templates/home.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>Home</title>
+</head>
+<body>
+<h1>Welcome to the Home Page!</h1>
+<a href="/logout">Logout</a>
+</body>
+</html>

+ 17 - 0
src/main/resources/templates/login.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>Login</title>
+</head>
+<body>
+<h1>Login</h1>
+<form action="/login" method="post">
+    <label for="username">Username:</label>
+    <input type="text" id="username" name="username" required><br>
+    <label for="password">Password:</label>
+    <input type="password" id="password" name="password" required><br>
+    <button type="submit">Login</button>
+</form>
+<p th:if="${param.error}" th:text="${param.error}">Error message</p>
+</body>
+</html>

+ 9 - 0
src/main/resources/templates/unauthorized.html

@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>Unauthorized</title>
+</head>
+<body>
+<h1>You are not authorized to access this page.</h1>
+</body>
+</html>

+ 0 - 13
src/test/java/com/zhentao/GooseCourseApplicationTests.java

@@ -1,13 +0,0 @@
-package com.zhentao;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class GooseCourseApplicationTests {
-
-    @Test
-    void contextLoads() {
-    }
-
-}

+ 47 - 0
src/test/java/com/zhentao/ShiroIntegrationTest.java

@@ -0,0 +1,47 @@
+package com.zhentao;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class ShiroIntegrationTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Test
+    public void testLoginSuccess() throws Exception {
+        mockMvc.perform(post("/login")
+                        .param("username", "admin")
+                        .param("password", "123456")
+                        .contentType(MediaType.APPLICATION_FORM_URLENCODED))
+                .andExpect(status().is3xxRedirection())
+                .andExpect(redirectedUrl("/home"));
+    }
+
+    @Test
+    public void testLoginFailure() throws Exception {
+        mockMvc.perform(post("/login")
+                        .param("username", "unknown")
+                        .param("password", "123")
+                        .contentType(MediaType.APPLICATION_FORM_URLENCODED))
+                .andExpect(status().is3xxRedirection())
+                .andExpect(redirectedUrl("/login?error=invalid"));
+    }
+
+    @Test
+    public void testAccessProtectedResourceWithoutLogin() throws Exception {
+        mockMvc.perform(get("/home"))
+                .andExpect(status().is3xxRedirection())
+                .andExpect(redirectedUrl("/login"));
+    }
+}