Shiro认证(Authentication)
Shiro简介:特性和架构
Apache Shiro是一个功能强大且易于使用的Java安全(权限)框架,提供了认证、授权、会话管理、加密、与Web集成、缓存等功能。Shiro不仅可以在JavaSE环境中使用,也可以在JavaEE环境中使用。
特性
- Authentication(认证):身份认证/登录,验证用户是否拥有相应的身份。
- Authorization(授权):权限验证,验证某个已认证的用户是否拥有某个权限。
- Session Management(会话管理):管理用户登录后的会话信息。
- Cryptography(加密):保护数据的安全性,如密码加密存储到数据库。
- Web Support:非常容易集成到Web环境。
- Caching:缓存用户信息、角色和权限,提高效率。
- Concurrency:支持多线程应用的并发验证。
- Remember Me:记住我功能,一次登录后下次无需再次登录。
架构
Shiro的架构从外部和内部来看可以分为两个部分:
-
外部架构:
- Subject:代表当前“用户”,与应用交互的任何东西都可以是Subject,如网络爬虫、机器人等。所有Subject都绑定到SecurityManager。
- SecurityManager:安全管理器,所有与安全有关的操作都会与SecurityManager交互,且它管理着所有Subject。它是Shiro的核心,负责与Shiro的其他组件进行交互。
- Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限)。SecurityManager要验证用户身份,需要从Realm获取相应的用户进行比较以确定用户身份是否合法。
-
内部架构:
- Authenticator:认证器,负责主体认证。
- Authorizer:授权器,决定主体是否有权限进行相应的操作。
- SessionManager:管理会话的生存周期。
- SessionDAO:数据访问对象,用于会话的CRUD操作。
- CacheManager:缓存控制器,管理用户、角色、权限等的缓存。
- Cryptography:密码模块,提供常见的加密组件用于加密/解密操作。
认证
Token
在Shiro中,认证指的是识别和证明操作者是一个合法用户。用户如果想要通过认证,需要提供Principal(身份)和Credentials(凭证),从而应用能验证用户身份。这些身份和凭证信息在Shiro框架中以Token(令牌)的概念进行封装。
快速上手
添加依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>2.0.1</version>
</dependency>
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope>
</dependency>
配置shiro.ini:在resource目录下创建shiro.ini文件配置用户认证数据。
# 对用户信息进行匹配
[users]
# 用户账号和密码
admin=123456
czkt=111111
认证测试:
public class ShiroTester { @Test public void testShiro() { IniRealm realm = new IniRealm("classpath:shiro.ini"); DefaultSecurityManager securityManager = new DefaultSecurityManager(); securityManager.setRealm(realm); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); try { subject.login(token); } catch (Exception e) { System.out.println("认证异常:"); e.printStackTrace(); } System.out.println("是否认证成功:" + subject.isAuthenticated()); System.out.println("身份信息:" + subject.getPrincipal()); }
}
认证流程
- 调用
Subject.login(Token)
进行登录,其会自动委托给SecurityManager
。 SecurityManager
负责真正的身份验证逻辑,它会委托给Authenticator
进行身份验证。Authenticator
可能会委托给相应的AuthenticationStrategy
进行多Realm身份验证。Authenticator
会把相应的Token传入Realm,从Realm获取身份信息,如果没有返回或抛出异常表示身份验证失败。
记住我 vs 认证
“记住我”功能允许用户在一次登录后,下次访问时无需再次登录。这与普通的认证流程有所不同,因为“记住我”功能通常会在用户的浏览器中存储一个持久化的Cookie,用于在用户下次访问时自动进行身份验证。
注销Logout
用户可以通过调用Subject.logout()
方法进行注销操作。这会委托给SecurityManager
进行实际的注销逻辑处理。
SpringBoot + Shiro 认证
1. 基础代码调整
首先,你需要在Spring Boot项目中添加Shiro的依赖。这可以通过在pom.xml
文件中添加以下依赖来完成:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> <!-- 请根据需要选择最新版本 -->
</dependency>
2. 自定义Realm
Realm是Shiro与数据源之间的桥梁,用于获取安全数据(如用户、角色和权限)。你需要创建一个自定义的Realm类,并实现AuthorizingRealm
接口。
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.beans.factory.annotation.Autowired; // 假设你有一个UserService用于处理用户相关的业务逻辑
@Component
public class CustomRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); // 从数据库中获取用户的角色和权限信息 Set<String> roles = userService.getRolesByUsername(username); Set<String> permissions = userService.getPermissionsByUsername(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); // 从数据库中获取用户信息 User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户不存在"); } // 将用户信息封装到SimpleAuthenticationInfo中,并返回 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( username, user.getPassword(), // 假设密码已经加密存储 ByteSource.Util.bytes(user.getSalt()), // 假设使用了盐值加密 getName() ); return authenticationInfo; }
}
3. 配置Shiro相关对象
你需要在Spring Boot的配置类中配置Shiro的相关对象,如SecurityManager
和ShiroFilterFactoryBean
。
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map; @Configuration
public class ShiroConfig { @Bean public SecurityManager securityManager(CustomRealm customRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 登录页面URL shiroFilterFactoryBean.setSuccessUrl("/index"); // 登录成功后的跳转页面 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 未授权页面URL // 定义过滤器链 Map<String, String> filterChainDefinitionMap = new HashMap<>(); filterChainDefinitionMap.put("/static/**", "anon"); // 静态资源无需认证 filterChainDefinitionMap.put("/login", "anon"); // 登录页面无需认证 filterChainDefinitionMap.put("/**", "authc"); // 其他URL都需要认证 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() { FilterRegistrationBean<DelegatingFilterProxy> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new DelegatingFilterProxy("shiroFilter")); registrationBean.addUrlPatterns("/*"); return registrationBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
}
4. IndexController重写登录方法
你需要创建一个控制器来处理登录请求。
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; @Controller
public class IndexController { @GetMapping("/login") public String login() { return "login"; // 返回登录页面 } @PostMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return "redirect:/index"; // 登录成功后重定向到首页 } catch (Exception e) { model.addAttribute("error", "用户名或密码错误"); return "login"; // 登录失败返回登录页面 } } @GetMapping("/index") public String index() { return "index"; // 返回首页 } @GetMapping("/403") public String accessDenied() { return "403"; // 返回未授权页面 }
}
5. 测试认证登录
现在,你可以启动Spring Boot应用程序,并访问/login
页面来测试登录功能。输入正确的用户名和密码后,你应该会被重定向到/index
页面。如果输入错误的用户名或密码,你应该会看到登录页面上的错误信息。
确保你的UserService
和数据库配置正确,以便能够正确地获取和验证用户信息。此外,你还需要配置一个视图解析器(如Thymeleaf或JSP)来渲染登录页面、首页和未授权页面。