1. Spring Security 简介与基础
1.1 Spring Security 概述
Spring Security 是 Spring 官方提供的安全框架,功能全面,与 Spring 生态无缝集成。
1.2 快速开始
添加依赖
XML
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
基础配置类
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
2. Spring Security 核心配置
2.1 数据库用户认证
java
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(getAuthorities(user.getRoles()))
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
}
private Collection<? extends GrantedAuthority> getAuthorities(
Set<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toList());
}
}
2.2 JWT 集成
java
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(Authentication authentication) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(authentication.getName())
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
}
2.3 方法级安全控制
java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
@Service
public class ProductService {
@PreAuthorize("hasRole('ADMIN') or hasPermission(#productId, 'Product', 'READ')")
public Product getProduct(Long productId) {
// 方法实现
}
@PostAuthorize("returnObject.owner == authentication.name")
public Product getProductDetails(Long id) {
// 方法实现
}
@PreFilter("filterObject.owner == authentication.name")
public void deleteProducts(List<Product> products) {
// 方法实现
}
}
3. Shiro 简介与基础
3.1 Shiro 概述
Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,相对轻量级。
3.2 快速开始
添加依赖
XML
<!-- Maven -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version>
</dependency>
基础配置
java
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 登录页面
shiroFilter.setLoginUrl("/login");
// 登录成功后的页面
shiroFilter.setSuccessUrl("/index");
// 未授权页面
shiroFilter.setUnauthorizedUrl("/unauthorized");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 静态资源允许匿名访问
filterChainDefinitionMap.put("/static/**", "anon");
// 登录页面允许匿名访问
filterChainDefinitionMap.put("/login", "anon");
// 需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
@Bean
public SecurityManager securityManager(Realm realm) {
DefaultWebSecurityManager securityManager =
new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public Realm realm() {
return new CustomRealm();
}
}
4. Shiro 核心配置
4.1 自定义 Realm
java
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 添加角色
Set<String> roles = userService.getRolesByUsername(username);
authorizationInfo.setRoles(roles);
// 添加权限
Set<String> permissions = userService.getPermissionsByUsername(username);
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("用户不存在");
}
if (user.getLocked()) {
throw new LockedAccountException("账户已锁定");
}
return new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
getName()
);
}
}
4.2 数据库集成
java
@Configuration
public class ShiroDatabaseConfig {
@Bean
public Realm jdbcRealm(DataSource dataSource) {
JdbcRealm realm = new JdbcRealm();
realm.setDataSource(dataSource);
realm.setAuthenticationQuery(
"SELECT password, salt FROM users WHERE username = ?");
realm.setUserRolesQuery(
"SELECT role_name FROM user_roles WHERE username = ?");
realm.setPermissionsQuery(
"SELECT permission FROM roles_permissions WHERE role_name = ?");
realm.setPermissionsLookupEnabled(true);
realm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
return realm;
}
}
4.3 缓存配置
java
@Configuration
public class ShiroCacheConfig {
@Bean
public CacheManager cacheManager() {
return new MemoryConstrainedCacheManager();
}
@Bean
public SecurityManager securityManager(Realm realm, CacheManager cacheManager) {
DefaultWebSecurityManager securityManager =
new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setCacheManager(cacheManager);
return securityManager;
}
}
4.4 会话管理
java
@Configuration
public class ShiroSessionConfig {
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800000); // 30分钟
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
}
5. 两大框架对比与选择
5.1 功能对比
| 特性 | Spring Security | Apache Shiro |
|---|---|---|
| 与Spring集成 | 原生支持,无缝集成 | 需要额外配置 |
| 学习曲线 | 较陡峭 | 相对平缓 |
| 功能完整性 | 非常完整 | 基本功能齐全 |
| 配置方式 | 注解/Java配置 | INI/Java配置 |
| 社区支持 | Spring官方,活跃 | Apache,活跃 |
| 微服务支持 | 优秀(Spring Cloud Security) | 需要自行扩展 |
| OAuth2 | 原生支持 | 需要扩展 |
5.2 选择建议
选择 Spring Security 当:
-
项目基于完整的 Spring 生态
-
需要 OAuth2、JWT 等现代安全协议
-
项目规模较大,需要细粒度的权限控制
-
使用 Spring Cloud 微服务架构
选择 Shiro 当:
-
项目相对简单,不需要复杂的权限控制
-
团队对 Shiro 更熟悉
-
需要更轻量级的安全解决方案
-
项目不是基于 Spring 全家桶
5.3 实际应用示例
Spring Security + JWT 的微服务安全架构:
XML
# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
jwk-set-uri: http://auth-server:9000/.well-known/jwks.json
jwt:
secret: your-jwt-secret-key-here
expiration: 86400000
Shiro 多 Realm 配置示例:
java
@Bean
public ModularRealmAuthenticator authenticator() {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return authenticator;
}
@Bean
public SecurityManager securityManager(List<Realm> realms) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealms(realms);
securityManager.setAuthenticator(authenticator());
return securityManager;
}
5.4 最佳实践
-
最小权限原则:只授予必要的最小权限
-
密码安全:使用 BCrypt 等强哈希算法
-
会话管理:合理设置会话超时时间
-
CSRF 防护:确保启用 CSRF 保护
-
安全审计:记录重要的安全事件
-
定期更新:保持安全框架版本更新
-
安全测试:定期进行安全漏洞扫描
总结
Spring Security 和 Apache Shiro 都是优秀的 Java 安全框架。Spring Security 更适合 Spring 生态的项目,功能全面但学习曲线较陡;Shiro 更轻量级,配置简单,适合中小型项目或非 Spring 项目。选择哪个框架应根据项目需求、团队技术栈和技术熟悉度来决定。
无论选择哪个框架,都要遵循安全最佳实践,确保应用程序的安全性。