Appearance
Spring Security
Spring Security 概述
Spring Security 是一个专注于身份认证和权限控制的安全框架。
Spring Security快速入门
1. 导入依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>lndhdx-230601-springsecurity</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.7</version>
</parent>
<dependencies>
<!-- spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 权限配置
java
package com.futureweaver.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(conf ->
conf
.requestMatchers("*.html", "*.js", "*.css")
.permitAll()
).authorizeHttpRequests(conf ->
conf
.anyRequest()
.authenticated()
).formLogin(conf ->
conf.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User
.withUsername("user")
.password("user")
.roles("USER")
.build();
UserDetails admin = User
.withUsername("admin")
.password("admin")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
3. 编写资源
- Application.java
java
package com.futureweaver.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.futureweaver")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- UserController.java
java
package com.futureweaver.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/query")
@ResponseBody
public List<String> query() {
List<String> result = new ArrayList<>();
result.add("zhangsan");
result.add("lisi");
result.add("wangwu");
return result;
}
}
- index.html
放到resource的static目录下
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>hello world!</h1>
</body>
</html>
基于注解的权限控制
1. 更改配置
java
package com.futureweaver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(conf ->
conf.permitAll()
).csrf(conf ->
conf.disable()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User
.withUsername("user")
.password("user")
.roles("USER")
.build();
UserDetails admin = User
.withUsername("admin")
.password("admin")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
2. 添加权限注解
java
package com.futureweaver.web;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/query")
@ResponseBody
@Secured("ROLE_ADMIN")
public List<String> query() {
List<String> result = new ArrayList<>();
result.add("zhangsan");
result.add("lisi");
result.add("wangwu");
return result;
}
}
基于注解的复杂权限控制
java
package com.futureweaver.web;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/add")
@ResponseBody
@PreAuthorize("isAuthenticated() and principal.username.equals('zhangsan') or hasAuthority('ROLE_ADMIN')")
public void add() {
}
}
基于RESTful风格的配置
待解决问题
出错时
- 未登录时,提示要登录页面修改
- 无权限时,提示错误信息[目前可行]
登录相关
- 登录服务地址变更
- 登录成功时的响应
- 登录失败时的响应
- 修改参数名username => realname; password => pwd
退出相关
- 退出服务地址变更
- 退出成功时的响应
解决
- SecurityConfig.java
java
package com.futureweaver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(conf ->
conf
.loginProcessingUrl("/admin/login")
.successForwardUrl("/admin/login_success")
.failureForwardUrl("/admin/login_failure")
.usernameParameter("realname")
.passwordParameter("pass")
.permitAll()
).logout(conf ->
conf
.logoutUrl("/admin/logout")
.logoutSuccessUrl("/admin/logout_success")
.permitAll()
).exceptionHandling(conf ->
// 401 Unauthorized 页面配置
// 403 Forbidden 页面配置
conf.authenticationEntryPoint(authenticationEntryPoint())
).authorizeHttpRequests(conf ->
conf
.anyRequest()
.permitAll()
).csrf(conf ->
conf.disable()
);
return http.build();
}
AuthenticationEntryPoint authenticationEntryPoint() {
return new BasicAuthenticationEntryPoint();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails zhangsan = User
.withUsername("zhangsan")
.password("123456")
.roles("USER")
.build();
UserDetails lisi = User
.withUsername("lisi")
.password("123456")
.roles("USER")
.build();
UserDetails admin = User
.withUsername("admin")
.password("admin")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(zhangsan, lisi, admin);
}
}
- AuthController
java
package com.futureweaver.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/admin")
public class AuthController {
@RequestMapping("/login_success")
@ResponseBody
public String loginSuccess() {
return "login success";
}
@RequestMapping("/login_failure")
@ResponseBody
public String loginFailure() {
return "login failure";
}
@RequestMapping("/logout_success")
@ResponseBody
public String logoutSuccess() {
return "logout success";
}
}
自定义UserDetailsService
如果想要实际地查询数据库,将数据库返回的数据库作为登录的依据
就必须使用springsecurity中的UserDetailsService和UserDetails完成
1. 导入依赖
com.mysql:mysql-connector-j
com.alibaba:druid-spring-boot-starter:1.2.20
org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2
2. 编写springboot配置文件
properties
# mybatis-settings
mybatis.configuration.lazy-loading-enabled=true
mybatis.configuration.auto-mapping-behavior=full
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.cache-enabled=false
mybatis.configuration.local-cache-scope=statement
# mybatis-typeAliases
mybatis.type-aliases-package=com.futureweaver.domain
# datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shopping?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
3. 给启动器添加MapperScan注解
java
package com.futureweaver.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.futureweaver")
@MapperScan("com.futureweaver.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. 删除SecurityConfig中的UserDetailsService的Bean配置
java
package com.futureweaver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(conf ->
conf
.loginProcessingUrl("/admin/login")
.successForwardUrl("/admin/login_success")
.failureForwardUrl("/admin/login_failure")
.usernameParameter("realname")
.passwordParameter("pass")
.permitAll()
).logout(conf ->
conf
.logoutUrl("/admin/logout")
.logoutSuccessUrl("/admin/logout_success")
.permitAll()
).exceptionHandling(conf ->
// 401 Unauthorized 页面配置
// 403 Forbidden 页面配置
conf.authenticationEntryPoint(authenticationEntryPoint())
).authorizeHttpRequests(conf ->
conf
.anyRequest()
.permitAll()
).csrf(conf ->
conf.disable()
);
return http.build();
}
AuthenticationEntryPoint authenticationEntryPoint() {
return new BasicAuthenticationEntryPoint();
}
}
5. 编写UserDetailsService的实现类
java
package com.futureweaver.service;
import com.futureweaver.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service("userDetailsService")
public class UserService implements UserDetailsService {
@Autowired
private UserMapper mapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return mapper.queryByUsername(username);
}
}
6. 编写UserDetails的实现类
java
package com.futureweaver.domain;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class User implements UserDetails {
// Apache Shiro
// Spring Security
private String id;
private String username;
private String password;
private String lastLoginIp;
private String lastLoginTime;
private String avatar;
private String roleIds;
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", lastLoginIp='" + lastLoginIp + '\'' +
", lastLoginTime='" + lastLoginTime + '\'' +
", avatar='" + avatar + '\'' +
", roleIds='" + roleIds + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getLastLoginIp() {
return lastLoginIp;
}
public void setLastLoginIp(String lastLoginIp) {
this.lastLoginIp = lastLoginIp;
}
public String getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(String lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getRoleIds() {
return roleIds;
}
public void setRoleIds(String roleIds) {
this.roleIds = roleIds;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}