Spring Security一自定义登录页面、连接数据库进行认证、url授权, 方法授权两种方式
目录一、自定义登录页面在快速上手中,你可能会想知道登录页面从哪里来的?因为我们并没有提供任何的HTML或JSP文件。Spring Security的默认配置没有明确设定一个登录页面的URL,因此Spring Security会根据启用的功能自动生成一个登录页面URL,并使用默认URL处理登录的提交内容,登录后跳转的到默认URL等等。尽管自动生成的登录页面很方便 快速启动和运行,但大多数应用程序都希
一、自定义登录页面
默认登录页面机制
Spring Security的默认配置没有明确设定登录页面URL,因此会根据启用的功能自动生成一个登录页面URL。尽管自动生成的登录页面很方便,但大多数应用程序都希望定义自己的登录页面。
自定义登录页面配置
核心配置方法:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
// 对密码进行编码, 使用不加密的对比
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
// 配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/r/**").authenticated() // 对所有/r/**请求必须认证通过,才可以访问
.anyRequest().permitAll() // 除了/r/**, 其他请求都可以访问
.and()
.formLogin()
.loginPage("/login.html") // 自定义登录页面
.loginProcessingUrl("/login") // 登录处理URL
.usernameParameter("username") // 用户名参数名
.passwordParameter("password") // 密码参数名
.successForwardUrl("/index") // 登录成功跳转页面
.failureForwardUrl("/login.html"); // 登录失败跳转页面
}
}
关键配置项:
loginPage():指定自定义登录页面路径loginProcessingUrl():指定登录表单提交的处理URLsuccessForwardUrl():登录成功后的跳转页面failureForwardUrl():登录失败后的跳转页面
注意事项:
- 自定义登录页面需要允许匿名访问
- 登录页面的表单action必须与
loginProcessingUrl()配置一致 - 用户名和密码的input name必须与配置的参数名一致
二、连接数据库认证
核心概念
前边的例子我们是将用户信息存储在内存中,实际项目中用户信息存储在数据库中。根据前边对认证流程研究,只需要重新定义UserDetailService即可实现根据用户账号查询数据库。
实现步骤
1. 添加数据库依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2. 创建用户实体类
@Entity
@Table(name = "users")
public class User {
@Id
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
// getter/setter...
}
3. 创建用户权限实体类
@Entity
@Table(name = "user_authorities")
public class UserAuthority {
@Id
private String userId;
private String authority;
// getter/setter...
}
4. 自定义UserDetailsService
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户信息
User user = userRepository.findByUsername(username);
if (user == null) {
return null;
}
// 查询用户权限
List<String> permissions = userRepository.findPermissionsByUserId(user.getId());
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
// 创建UserDetails对象
UserDetails userDetails = User.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(permissionArray)
.build();
return userDetails;
}
}
核心要点:
- 只需要实现UserDetailsService接口
- Spring Security会自动调用loadUserByUsername方法
- 返回的UserDetails对象包含用户名、密码和权限信息
三、会话 (SecurityContextHolder上下文)
用户认证通过后,为了避免用户的每次操作都进行认证,可将用户的信息保存在会话中。spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。
SecurityContextHolder结构
获取当前用户信息:
// 获取当前认证用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
SecurityContextHolder三种存储模式:
MODE_THREADLOCAL:默认模式,SecurityContext存储在ThreadLocal中MODE_INHERITABLETHREADLOCAL:SecurityContext存储在InheritableThreadLocal中,子线程可以获取父线程的SecurityContextMODE_GLOBAL:SecurityContext在所有线程中都相同
会话控制
会话超时配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.maximumSessions(1) // 最大会话数
.maxSessionsPreventsLogin(false) // 达到最大会话数时是否阻止新的登录请求
.sessionRegistry(sessionRegistry()) // 会话注册表
.and()
.sessionFixation().migrateSession() // 会话固定攻击保护
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); // 会话创建策略
}
会话创建策略:
ALWAYS:总是创建HttpSessionIF_REQUIRED:Spring Security需要时创建HttpSession(默认)NEVER:Spring Security不会创建HttpSession,但如果应用创建了会使用它STATELESS:Spring Security不会创建HttpSession,也不会使用它
四、注销功能
注销配置
基本注销配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutUrl("/logout") // 注销处理URL
.logoutSuccessUrl("/login?logout") // 注销成功后跳转页面
.deleteCookies("JSESSIONID") // 删除指定的cookie
.invalidateHttpSession(true) // 使HttpSession失效
.clearAuthentication(true); // 清除认证信息
}
注销处理器配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutUrl("/logout")
.addLogoutHandler(new SecurityContextLogoutHandler()) // 添加注销处理器
.logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler()); // 自定义注销成功处理器
}
核心要点:
logoutUrl():指定注销请求的URLlogoutSuccessUrl():注销成功后的跳转页面invalidateHttpSession(true):使当前HttpSession失效clearAuthentication(true):清除SecurityContextHolder中的认证信息deleteCookies():删除指定的cookie
五、授权
授权概念
授权:用户认证通过后,根据用户的权限来控制用户访问资源的过程。
授权实现方式
1. 基于URL的授权
通过配置URL路径和所需权限的映射关系来控制访问:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 管理员权限
.antMatchers("/user/**").hasRole("USER") // 用户权限
.antMatchers("/public/**").permitAll() // 公开访问
.anyRequest().authenticated(); // 其他请求需要认证
}
2. 基于表达式的授权
使用SpEL表达式进行更复杂的权限控制:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')")
.antMatchers("/user/**").access("hasRole('USER') or hasRole('ADMIN')")
.anyRequest().authenticated();
}
常用权限表达式:
hasRole('ROLE'):检查用户是否具有指定角色hasAuthority('AUTHORITY'):检查用户是否具有指定权限hasAnyRole('ROLE1', 'ROLE2'):检查用户是否具有任意一个指定角色hasAnyAuthority('AUTH1', 'AUTH2'):检查用户是否具有任意一个指定权限permitAll():允许所有用户访问denyAll():拒绝所有用户访问authenticated():要求用户已认证anonymous():允许匿名用户访问
5.1 web授权
配置Web授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated() // 所有/r/**的请求必须认证通过
.anyRequest().permitAll() // 除了/r/**,其它的请求可以访问
.and()
.formLogin() // 允许表单登录
.successForwardUrl("/login-success"); // 自定义登录成功的页面地址
}
权限控制规则
antMatchers():匹配特定的URL路径hasAuthority():要求用户具有特定权限authenticated():要求用户已认证permitAll():允许所有用户访问
匹配器类型
antMatchers("/admin/**"):Ant风格路径匹配regexMatchers(".*[.]js"):正则表达式匹配mvcMatchers("/api/**"):Spring MVC路径匹配
5.2 方法授权 (@PreAuthorize、@PostAuthorize、@Secured)
启用方法级安全
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
// 配置类
}
1. @PreAuthorize(方法执行前验证)
在方法执行前进行权限验证:
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(String userId) {
// 只有ADMIN角色才能执行
}
@PreAuthorize("hasAuthority('USER_READ') or hasRole('ADMIN')")
public User getUserInfo(String userId) {
// 需要USER_READ权限或ADMIN角色
}
@PreAuthorize("#userId == authentication.name or hasRole('ADMIN')")
public User updateUser(String userId, User user) {
// 只能修改自己的信息或管理员可以修改任何用户
}
2. @PostAuthorize(方法执行后验证)
在方法执行后对返回结果进行权限验证:
@PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')")
public Document getDocument(String docId) {
// 只能获取自己拥有的文档或管理员可以获取任何文档
}
@PostAuthorize("hasPermission(returnObject, 'read')")
public User getUserById(String userId) {
// 对返回的用户对象进行读取权限验证
}
3. @Secured(简单角色验证)
基于角色的简单权限验证:
@Secured("ROLE_ADMIN")
public void adminOperation() {
// 只有ADMIN角色才能执行
}
@Secured({"ROLE_USER", "ROLE_ADMIN"})
public void userOperation() {
// USER或ADMIN角色都可以执行
}
4. @PreFilter和@PostFilter(集合过滤)
@PreFilter("hasPermission(filterObject, 'delete')")
public void deleteUsers(List<User> users) {
// 过滤掉没有删除权限的用户
}
@PostFilter("hasPermission(filterObject, 'read')")
public List<Document> getDocuments() {
// 过滤掉没有读取权限的文档
}
核心要点:
@PreAuthorize:方法执行前验证,支持SpEL表达式@PostAuthorize:方法执行后验证,可以访问返回值@Secured:简单的角色验证,不支持表达式@PreFilter/@PostFilter:对集合参数或返回值进行过滤
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)