Hauptinhalt
- Spring Security-Modul
- verwenden
- Legen Sie Benutzernamen und Passwort fest
- speicherbasiert
- Basierend auf der UserDetailsService-Schnittstelle
- Basierend auf der Konfigurationsklasse WebSecurityConfigurerAdapter
- Basierend auf der DB-Benutzerrollenberechtigung
- benutzerdefinierte Anmeldeseite
- Anmeldeauthentifizierungsprozess
- Anpassungserfolg, Anpassungsfehler
- Sitzungsverwaltung (Sitzung)
- Sitzungskontrolle
- Session-Timeout
- Sitzungs-Parallelitätskontrolle
- Cluster-Sitzung
- sicheres Sitzungscookie
- Mich erinnern
- austragen
- CSRF
- Prinzip
- Benutzerautorisierung (Zugriffskontrolle)
- Web-Autorisierung: URL-Abfangen für Autorisierung FilterSecurityInterceptor
- URL-Abgleich zur Zugriffskontrolle
- RequestMatcher-Schnittstelle
- integrierte Zugangskontrolle
- Benutzerdefiniertes 403-Verarbeitungsschema
- Ausdrucksbasierte Zugriffskontrolle
- Benutzerdefinierte Methode ExceptionTranslationFilter
- Methodenautorisierung: Methodenabfang zur Autorisierung MethodSecurityInterceptor
- JSR-250-Anmerkungen
- @Secured-Anmerkung
- Anmerkungen, die Ausdrücke unterstützen
- Berechtigungsprinzip
- Autorisierungsprozess
- Spring Security-Implementierungsprinzip @EnableWebSecurity
- Analyse des Mainline-Quellcodes
Spring Security-Modul
- Kernmodul – spring-security-core.jar: Enthält Kernauthentifizierungs- und Zugriffskontrollklassen und -schnittstellen, grundlegende Konfigurations-API für Remote-Support, ist ein Basismodul
- Remote-Aufruf – spring-security-remoting.jar: Bietet Integration mit Spring Remoting
- Webseite – spring-security-web.jar: Enthält Website-Sicherheitsmodule, bietet Website-Authentifizierungsdienste und URL-basierte Zugriffskontrolle
- Konfiguration – spring-security-config.jar: Enthält Sicherheits-Namespace-Parsing-Code, erforderlich, wenn XML für die Konfiguration verwendet wird
- LDAP – spring-security-ldap.jar: LDAP-Authentifizierung und -Konfiguration, wenn LDAP-Authentifizierung und Verwaltung von LDAP-Benutzerentitäten erforderlich sind
- ACL-Zugriffskontrollliste – spring-security-acl.jar: ACL-Implementierung (Access Control List) spezialisierter Domänenobjekte
- CAS - spring-security-cas.jar: CAS (Central Authentication Service) * Client-Vererbung, wenn Sie die CAS-SSO-Server-Webseitenüberprüfung verwenden möchten
- OpenID – spring-security-openid.jar: Unterstützung für OpenID-Webauthentifizierung
- Test – spring-security-test.jar: unterstützt Spring Security-Tests
verwenden
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 实现对 Spring Security 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
@RestController
@RequestMapping("/admin")
public class AdminController {
@RequestMapping("/demo")
public String demo() {
return "spring security demo";
}
}
http://localhost:8080/admin/demo
login DefaultLoginPageGeneratingFilter
Anmeldung ist erforderlich, Standardbenutzername: Benutzer, Passwort kann durch Anzeigen des Konsolenprotokolls abgerufen werden
Legen Sie Benutzernamen und Passwort fest
UserDetailsServiceAutoConfiguration -> UserDetailsService
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
- speicherbasiert
spring:
security:
user:
name: mendd
password: 123456
- Basierend auf der UserDetailsService-Schnittstelle
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new org.springframework.security.core.userdetails.User("mendd", "123456", AuthorityUtils.commaSeparatedStringToAuthorityList("admin, user"));
}
}
PasswordEncoderFactories
public class PasswordEncoderFactories {
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
Geben Sie die Verschlüsselungsmethode BCrypt an, die offizielle Verschlüsselungsmethode, die von Spring Security empfohlen wird
@Configuration
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
}
UserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//自定义逻辑封装
String password = passwordEncoder.encode("123456");
// return new org.springframework.security.core.userdetails.User("mendd", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin, user"));
return User.withUsername("mendd").password(password).authorities("admin","user").build();
}
}
- Basierend auf der Konfigurationsklasse WebSecurityConfigurerAdapter
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(myUserDetailsService);
auth.inMemoryAuthentication()
.withUser("mendd1")
.password(passwordEncoder().encode("123456"))
.authorities("admin");
}
}
- DB-Benutzerrolle-Berechtigung
DB
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(64) NOT NULL COMMENT '密码,加密存储',
`phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
`email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE,
UNIQUE KEY `phone` (`phone`) USING BTREE,
UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='用户表';
insert into `tb_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values
(37,'mendd','$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi','158xxxxxxx','[email protected]','2019-04-04 23:21:27','2019-04-04 23:21:29');
CREATE TABLE `tb_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父角色',
`name` varchar(64) NOT NULL COMMENT '角色名称',
`enname` varchar(64) NOT NULL COMMENT '角色英文名称',
`description` varchar(200) DEFAULT NULL COMMENT '备注',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='角色表';
insert into `tb_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values
(37,0,'超级管理员','mendd',NULL,'2019-04-04 23:22:03','2019-04-04 23:22:05');
CREATE TABLE `tb_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户 ID',
`role_id` bigint(20) NOT NULL COMMENT '角色 ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='用户角色表';
insert into `tb_user_role`(`id`,`user_id`,`role_id`) values
(37,37,37);
CREATE TABLE `tb_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父权限',
`name` varchar(64) NOT NULL COMMENT '权限名称',
`enname` varchar(64) NOT NULL COMMENT '权限英文名称',
`url` varchar(255) NOT NULL COMMENT '授权路径',
`description` varchar(200) DEFAULT NULL COMMENT '备注',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8 COMMENT='权限表';
insert into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values
(37,0,'系统管理','System','/',NULL,'2019-04-04 23:22:54','2019-04-04 23:22:56'),
(38,37,'用户管理','SystemUser','/users/',NULL,'2019-04-04 23:25:31','2019-04-04 23:25:33'),
(39,38,'查看用户','SystemUserView','',NULL,'2019-04-04 15:30:30','2019-04-04 15:30:43'),
(40,38,'新增用户','SystemUserInsert','',NULL,'2019-04-04 15:30:31','2019-04-04 15:30:44'),
(41,38,'编辑用户','SystemUserUpdate','',NULL,'2019-04-04 15:30:32','2019-04-04 15:30:45'),
(42,38,'删除用户','SystemUserDelete','',NULL,'2019-04-04 15:30:48','2019-04-04 15:30:45'),
(44,37,'内容管理','SystemContent','/contents/',NULL,'2019-04-06 18:23:58','2019-04-06 18:24:00'),
(45,44,'查看内容','SystemContentView','/contents/view/**',NULL,'2019-04-06 23:49:39','2019-04-06 23:49:41'),
(46,44,'新增内容','SystemContentInsert','/contents/insert/**',NULL,'2019-04-06 23:51:00','2019-04-06 23:51:02'),
(47,44,'编辑内容','SystemContentUpdate','/contents/update/**',NULL,'2019-04-06 23:51:04','2019-04-06 23:51:06'),
(48,44,'删除内容','SystemContentDelete','/contents/delete/**',NULL,'2019-04-06 23:51:08','2019-04-06 23:51:10');
CREATE TABLE `tb_role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL COMMENT '角色 ID',
`permission_id` bigint(20) NOT NULL COMMENT '权限 ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
insert into `tb_role_permission`(`id`,`role_id`,`permission_id`) values
(37,37,37),
(38,37,38),
(39,37,39),
(40,37,40),
(41,37,41),
(42,37,42),
(43,37,44),
(44,37,45),
(45,37,46),
(46,37,47),
(47,37,48);
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
Anwendung
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql.localhost.com:3306/oauth2-test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: root
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
UserDetailsService
public interface UserService extends UserDetailsService {
User getByUsername(String username);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public User getByUsername(String username) {
return userMapper.getByUsername(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("自定义登录逻辑");
//从mysql查询用户
User user = getByUsername(username);
List<GrantedAuthority> authorities = new ArrayList<>();
if(user!=null){
List<Permission> permissions = permissionMapper.findByUserId(user.getId());
//设置权限
permissions.forEach(permission -> {
if (permission!=null && !StringUtils.isEmpty(permission.getEnname())){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getEnname());
authorities.add(grantedAuthority);
}
});
// 封装成UserDetails的实现类
return new org.springframework.security.core.userdetails.User(
user.getUsername(),user.getPassword(),authorities);
}else {
throw new UsernameNotFoundException("用户名不存在");
}
}
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置UserDetailsService的实现类
auth.userDetailsService(userService);
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
benutzerdefinierte Anmeldeseite
Die Standard-Anmeldeseite wird von DefaultLoginPageGeneratingFilter#generateLoginPageHtml generiert
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/user/login" method="post">
用户名: <input type="text" name="username1"/>
密码: <input type="password" name="password1"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
WebSecurityConfig.configure(HttpSecurity)
@Configuration
@EnableWebSecurity //spring-boot-starter-security依赖不需要添加@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //POST
.usernameParameter("username1")
.passwordParameter("password1")
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
//.defaultSuccessUrl("/main.html") // SavedRequestAwareAuthenticationSuccessHandler AuthenticationSuccessHandler
.successForwardUrl("/tomain") //ForwardAuthenticationSuccessHandler AuthenticationSuccessHandler
.failureForwardUrl("/toerror"); //ForwardAuthenticationFailureHandler AuthenticationFailureHandler
http.authorizeRequests()
.antMatchers("/login.html","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated() //需要认证
.and().csrf().disable(); //关闭csrf防护 from input token
}
}
Anmeldeauthentifizierungsprozess
UsernamePasswordAuthenticationFilter#doFilterAuthentication
ProviderManager
#authenticate
AbstractUserDetailsAuthenticationProvider#authenticate => UserDetails abrufen und Passwort überprüfen
DaoAuthenticationProvider#retrieveUser-
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
Authentifizierungsausnahme (failureHandler)
Authentifizierungserfolg (successHandler)
Anpassungserfolg, Anpassungsfehler
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //POST
.usernameParameter("username1")
.passwordParameter("password1")
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
// .defaultSuccessUrl("/main.html") // SavedRequestAwareAuthenticationSuccessHandler AuthenticationSuccessHandler
// .successForwardUrl("/tomain") //ForwardAuthenticationSuccessHandler AuthenticationSuccessHandler
// .failureForwardUrl("/toerror"); //ForwardAuthenticationFailureHandler AuthenticationFailureHandler
.successHandler(new MyAuthenticationSuccessHandler("/main.html"))
.failureHandler(new MyAuthenticationFailureHandler("/error.html"));
http.authorizeRequests()
.antMatchers("/login.html","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated() //需要认证
.and().csrf().disable(); //关闭csrf防护 from input token
}
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String redirectUrl;
public MyAuthenticationSuccessHandler(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(redirectUrl);
}
}
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String redirectUrl;
public MyAuthenticationFailureHandler(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect(redirectUrl);
}
}
Sitzungsverwaltung (Sitzung)
Nachdem der Benutzer authentifiziert wurde, können die Informationen des Benutzers in der Sitzung gespeichert werden, um eine Authentifizierung für jeden Vorgang des Benutzers zu vermeiden. Spring Security bietet Sitzungsverwaltung. Nach bestandener Authentifizierung werden die Identitätsinformationen in den SecurityContextHolder-Kontext eingefügt und der SecurityContext an den aktuellen Thread gebunden, um den Erwerb der Benutzeridentität zu erleichtern.
Der sitzungspersistente Filter SecurityContextPersistenceFilter
wird im aktuellen Thread-Kontext platziertThreadLocal<SecurityContext>
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
//private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
AbstractAuthenticationProcessingFilter#successfulAuthentication
SecurityContextHolder.getContext().setAuthentication(authResult);
@RestController
@RequestMapping("/admin")
public class AdminController {
@RequestMapping("/demo")
public String demo() {
return "spring security demo";
}
@RequestMapping("/currentUser")
public Object currentUser() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
{
authorities: [
....
{
authority: "SystemUserView"
},
...
],
details: {
remoteAddress: "0:0:0:0:0:0:0:1",
sessionId: "56BC3C1CD1776003C8E5CA08FF204469",
},
authenticated: true,
principal: {
password: null,
username: "mendd",
authorities: [
....
{
authority: "SystemUserView"
},
...
],
accountNonExpired: true,
accountNonLocked: true,
credentialsNonExpired: true,
enabled: true,
},
credentials: null,
name: "mendd",
}
Sitzungskontrolle
Die SessionManagementFilter-Sitzung kann in Redis abgelegt werden, jetzt wird sie im Speicher gespeichert
Mechanismus | beschreiben |
---|---|
stets | Wenn die Sitzung nicht vorhanden ist, muss sie immer erstellt werden |
Falls erforderlich | Erstellen Sie bei Bedarf eine Sitzung (Standardeinstellung) beim Anmelden |
niemals | Spring Security erstellt keine Sitzung, aber wenn eine Sitzung an anderer Stelle in der Anwendung erstellt wird, wird sie von Spring Security verwendet |
staatenlos | Spring Security wird niemals eine Sitzung erstellen oder verwenden. Und es bedeutet, dass keine Cookies verwendet werden, sodass jede Anfrage erneut authentifiziert werden muss. Diese zustandslose Architektur funktioniert gut mit REST-APIs und ihrem zustandslosen Authentifizierungsmechanismus. |
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}
Session-Timeout
Sie können das Sitzungszeitlimit im Sevlet-Container wie folgt festlegen, um die Gültigkeitsdauer der Sitzung auf 600 Sekunden festzulegen; Spring-Boot-Konfigurationsdatei:
server:
servlet:
session:
timeout: 60s #default 30m
Hinweis: Die Mindestsitzungsdauer beträgt 60 Sekunden. Weitere Informationen finden Sie im Quellcode TomcatServletWebServerFactory#configureSession:
private long getSessionTimeoutInMinutes() {
Duration sessionTimeout = getSession().getTimeout();
if (isZeroOrLess(sessionTimeout)) {
return 0;
}
return Math.max(sessionTimeout.toMinutes(), 1);
}
@RestController
@RequestMapping("/session")
public class SessionController {
@GetMapping("/invalid")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public String sessionInvalid() {
return "session失效";
}
}
Sitzungs-Parallelitätskontrolle
Nachdem sich der Benutzer auf diesem Mobiltelefon angemeldet hat, meldet er sich auf einem anderen Mobiltelefon mit demselben Konto an. Es geht darum, ob das zuvor angemeldete Konto ausgeführt werden muss oder es auf die zweite Anmeldung beschränkt werden muss oder wie Tencent Video-VIP-Konto, höchstens fünf Personen können sich gleichzeitig anmelden, die sechste Person darf sich nicht anmelden.
ConcurrentSessionFilter speichert
die ConcurrentSessionControlAuthenticationStrategy-Strategie
- MaximumSessions: Die maximale Anzahl von Sitzungen. Wenn Sie auf 1 setzen, bedeutet dies, dass ein Benutzer nur eine Sitzung haben kann
- ExpiredSessionStrategy: Sitzungsablaufstrategie
http.sessionManagement().invalidSessionUrl("/session/invalid")
.maximumSessions(1)
.expiredSessionStrategy(new MySessionInformationExpiredStrategy());
Benutzerdefinierte Sitzungsstrategie SessionInformationExpiredStrategy
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
HttpServletResponse response = event.getResponse();
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("您已被挤兑下线!");
}
}
Verhindern Sie, dass sich Benutzer ein zweites Mal anmelden
sessionManagement kann auch den booleschen Wert „maxSessionsPreventsLogin“ konfigurieren, der die Anmeldung verhindert, wenn die durch „maximumSessions“ festgelegte maximale Anzahl von Sitzungen erreicht ist.
http.sessionManagement().invalidSessionUrl("/session/invalid")
.maximumSessions(1)
.expiredSessionStrategy(new MySessionInformationExpiredStrategy())
.maxSessionsPreventsLogin(true);
session 过期之后之前的无法登陆了, 正式不建议使用默认的需要自己实现
Zur Verwendung muss konfiguriert werdenmaxSessionsPreventsLogin = false
http.sessionManagement().invalidSessionUrl("/session/invalid")
.maximumSessions(1)
.expiredSessionStrategy(new MySessionInformationExpiredStrategy())
.maxSessionsPreventsLogin(false);
Cluster-Sitzung
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
spring:
session:
store-type: redis
redis:
host: redis.localhost.com
database: 5
password: pwd
SessionManagementFilter
SecurityContextPersistenceFilter
SessionRepositoryFilter#doFilterInternal
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
//SessionRepositoryFilter.this.sessionRepository.save(session);
}
SessionId wird in das Cookie geschrieben
Mangel:
Spring Session + Redis hat einen sehr großen Fehler bei der Implementierung der verteilten Sitzungsfreigabe: Es kann keine Sitzungen über Domänennamen hinweg teilen. Es kann nur Sitzungen auf einem einzelnen Server teilen, da es auf Cookies basiert und Cookies nicht domänenübergreifend sein können. Spring Session wird im Allgemeinen zum Teilen von Sitzungen verwendet, wenn mehrere Server einen Lastausgleich durchführen. Sie haben alle denselben Domänennamen und sind nicht domänenübergreifend.
wirklich nutzen oder brauchenoauth2
sicheres Sitzungscookie
Wir können die httpOnly- und Secure-Tags verwenden, um unser Sitzungscookie zu schützen:
- httpOnly: Wenn true, können Browserskripte nicht auf Cookies zugreifen
- sicher: Wenn true, wird das Cookie nur über HTTPS-Verbindungen gesendet
Spring-Boot-Konfigurationsdatei:
server:
servlet:
session:
timeout: 60s #default 30m
cookie:
http-only: true #httpOnly:如果为true,那么浏览器脚本将无法访问cookie
secure: true #如果为true,则cookie将仅通过HTTPS连接发送
RememberMe = RememberMeAuthenticationFilter
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
)
@Autowired
public DataSource dataSource;
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//设置数据源
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//记住我
http.rememberMe()
.tokenRepository(persistentTokenRepository())//设置持久化仓库
.tokenValiditySeconds(3600) //超时时间,单位s 默认两周
.userDetailsService(userService); //设置自定义登录逻辑
}
<input type="checkbox" name="remember-me" value="true"/><br/>
LogoutLogoutFilter
Spring Security implementiert standardmäßig die Abmeldung, und der Benutzer muss lediglich eine /logout-Exit-Anfrage an das Spring Security-Projekt senden.
Benutzerdefinierte Exit-Logik
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html");
SecurityContextLogoutHandler
Wenn der Abmeldevorgang ausgelöst wird, geschieht Folgendes:
- Zerstören Sie das HTTPSession-Objekt
- Klarer Authentifizierungsstatus
- Wechseln Sie zu /login.html
LogoutSuccessHandler
beendet den Erfolgshandler, implementiert die LogoutSuccessHandler-Schnittstelle und kann die Verarbeitungslogik für den Exit-Erfolg anpassen.
CSRF CsrfFilter
CSRF (Cross-Site Request Forgery) Cross-Site Request Forgery, auch bekannt als „OneClick Attack“ oder Session Riding. Illegale Zugriffsanfragen auf vertrauenswürdige Websites durch gefälschte Benutzeranfragen.
Domänenübergreifend: Solange sich Netzwerkprotokoll, IP-Adresse und Port unterscheiden, handelt es sich um eine domänenübergreifende Anfrage.
Wenn der Client mit dem Dienst interagiert, werden Cookies eingeführt, um die Client-Identität aufzuzeichnen, da das HTTP-Protokoll selbst ein zustandsloses Protokoll ist. Die Sitzungs-ID wird im Cookie gespeichert, um den Client zu identifizieren. Im Fall einer domänenübergreifenden Sitzung kann die Sitzungs-ID in böswilliger Absicht von einem Dritten gekapert werden. Wenn über diese Sitzungs-ID eine Anfrage an den Server gestellt wird, betrachtet der Server die Anfrage als legal und es können viele unerwartete Dinge passieren.
Verteidigung gegen CSRF-Angriffsstrategie
- Validieren Sie das HTTP-Referer-Feld
- Fügen Sie der Anforderungsadresse ein Token hinzu und überprüfen Sie es
- Benutzerdefinierte Attribute in HTTP-Headern und -Validierung
Ab Spring Security4 ist der CSRF-Schutz standardmäßig aktiviert und Anforderungen werden standardmäßig für die CSRF-Verarbeitung abgefangen. Um sicherzustellen, dass nicht von anderen Websites Dritter darauf zugegriffen wird, erfordert CSRF die Verwendung eines Parameters namens _csrf als Token (das Token wird serverseitig generiert und beim Rendern der angeforderten Seite in die Seite eingebettet). Wenn Das Token stimmt erfolgreich mit dem Token auf der Serverseite überein, der Zugriff erfolgt normal.
@Configuration
public class CSRFWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("mendd")
.password(passwordEncoder().encode("123456"))
.authorities("admin");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/showLogin")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/main.html");
http.authorizeRequests()
.antMatchers("/showLogin").permitAll()
.anyRequest().authenticated();
// http.csrf().disable();
}
}
@Controller
public class LoginController {
@RequestMapping("/showLogin")
public String showLogin() {
return "login";
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
用户名: <input type="text" name="username1"/><br/>
密码: <input type="password" name="password1"/><br/>
记住我: <input type="checkbox" name="remember-me" value="true"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
Konfigurationsklasse ändern
http.csrf().disable(); //关闭csrf防护
Prinzip
Wie führt Spring Security die Authentifizierung durch?
- Der Benutzername und das Passwort werden vom Filter abgerufen und in die Authentifizierung gekapselt, normalerweise die Implementierungsklasse UsernamePasswordAuthenticationToken
- AuthenticationManager Identity Manager ist für die Überprüfung dieser Authentifizierung verantwortlich
- Nach erfolgreicher Authentifizierung gibt der Identitätsmanager AuthenticationManager eine mit Informationen gefüllte Authentifizierungsinstanz zurück (einschließlich der oben genannten Berechtigungsinformationen, Identitätsinformationen und Details, das Kennwort wird jedoch normalerweise entfernt).
- Der SecurityContextHolder-Sicherheitskontextcontainer legt die in Schritt 3 mit Informationen gefüllte Authentifizierung über die Methode SecurityContextHolder.getContext().setAuthentication(…) fest
entsprechende Schnittstelle
- Der Authentifizierungsmanager AuthenticationManager wird zur Verarbeitung einer Authentifizierungsanforderung verwendet und stellt die Eingabe der Authentifizierungsmethode bereit
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
-
ProviderManager ist eine Implementierungsklasse von AuthenticationManager, die grundlegende Authentifizierungslogik und -methoden bereitstellt. Sie enthält ein List-Attribut, das eine Vielzahl von Authentifizierungsmethoden über die AuthenticationProvider-Schnittstelle erweitert. Tatsächlich handelt es sich hierbei um die Anwendung des Delegatormodus (Delegate).
-
Die Authentifizierung stellt die höchste Ebene der Identitäts-/Authentifizierungsabstraktion in Spring Security dar. Über diese Schnittstelle der obersten Ebene können wir die Liste der Autoritätsinformationen, die Benutzern gehören, Passwörter, Benutzerdetails, Benutzeridentitätsinformationen und Authentifizierungsinformationen abrufen.
-
UsernamePasswordAuthenticationToken implementiert die Authentifizierung, die hauptsächlich den vom Benutzer eingegebenen Benutzernamen und das Kennwort kapselt und sie zur Überprüfung an AuthenticationManager weiterleitet. Nach Abschluss der Überprüfung wird ein Authentifizierungsobjekt mit erfolgreicher Authentifizierung zurückgegeben
public interface Authentication extends Principal, Serializable {
//1.权限信息列表,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串权限集合
Collection<? extends GrantedAuthority> getAuthorities();
//2.密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
Object getCredentials();
//3.认证时包含的一些信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
Object getDetails();
//4.身份信息,大部分情况下返回的是UserDetails接口的实现类
Object getPrincipal();
//5.是否被认证,认证为true
boolean isAuthenticated();
//6.设置是否能被认证
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
-
SecurityContextHolder wird zum Speichern von Informationen zum Sicherheitskontext (Sicherheitskontext) verwendet, und SecurityContextHolder verwendet standardmäßig die ThreadLocal-Strategie zum Speichern von Authentifizierungsinformationen
SecurityContextHolder.getContext().getAuthentication()
-
UserDetailsService
public interface UserDetailsService {
// 根据用户名加载用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
-
UserDetails-Benutzerinformationskernschnittstelle, die Standardimplementierungsklasse org.springframework.security.core.userdetails.User
-
PasswordEncoder BCryptPasswordEncoder ist der offizielle Passwort-Parser, der von Spring Security empfohlen wird. BCryptPasswordEncoder ist eine spezifische Implementierung der starken Hash-Methode von bcrypt. Es handelt sich um eine Einwegverschlüsselung, die auf dem Hash-Algorithmus basiert. Die Verschlüsselungsstärke kann durch die Stärke gesteuert werden, der Standardwert ist 10.
String passwd = BCrypt.hashpw("123",BCrypt.gensalt());
boolean checkpw = BCrypt.checkpw("123", passwd);
Benutzerautorisierung (Zugriffskontrolle)
- Web-Autorisierung: URL-Abfangen für Autorisierung FilterSecurityInterceptor
- Methodenautorisierung: Methodenabfang zur Autorisierung MethodSecurityInterceptor
Anonyme Benutzer, die nicht angemeldet sind: anonymerBenutzer AnonymousAuthenticationFilter
Web-Autorisierung: URL-Abfangen für Autorisierung FilterSecurityInterceptor
http.authorizeRequests()
//设置哪些路径可以直接访问,不需要认证
.antMatchers("/user/login","/login.html").permitAll()
.anyRequest().authenticated(); //需要认证才能访问
URL-Abgleich zur Zugriffskontrolle
- anyRequest() bedeutet, alle Anfragen abzugleichen
.anyRequest().authenticated()
- antMatchers(String… antPatterns)
.antMatchers("/js/**","/css/**").permitAll()
Platzhalter | veranschaulichen |
---|---|
? | entspricht jedem einzelnen Zeichen |
* | entspricht 0 oder einer beliebigen Anzahl von Zeichen |
** | entspricht 0 oder mehr Verzeichnissen |
- regexMatchers() Verwenden Sie reguläre Ausdrücke zum Abgleichen
.regexMatchers( ".+[.]js").permitAll()
- mvcMatchers() gilt für den Fall, dass servletPath konfiguriert ist
spring.mvc.servlet.path=/web
&.mvcMatchers("/admin/demo").servletPath("/web").permitAll()
RequestMatcher-Schnittstelle
Implementierungsklasse | einführen |
---|---|
AnyRequestMatcher |
passt zu jeder Anfrage |
AntPathRequestMatcher |
Passen Sie Anfragen mit ant gestalteten Pfad-Matching-Vorlagen an |
ELRequestMatcher |
Verwenden Sie EL Ausdrücke, um Anfragen zuzuordnen |
IpAddressMatcher |
IP Matching-Anfragen basierend auf Adresse, Support IPv4 undIPv6 |
MediaTypeRequestMatcher |
Basierend auf MediaType passenden Anfragen |
RegexRequestMatcher |
Übereinstimmungsanfragen basierend auf regulären Ausdrücken |
RequestHeaderRequestMatcher |
Übereinstimmungsanfragen basierend auf dem Vergleich der Header-Werte |
AndRequestMatcher |
and Kombinieren Sie mehrereRequestMatcher |
OrRequestMatcher |
or Kombinieren Sie mehrereRequestMatcher |
NegatedRequestMatcher |
not a betreibenRequestMatcher |
MvcRequestMatcher |
Match- Spring MVC Anfrage verwendetHandlerMappingIntrospector |
integrierte Zugangskontrolle
-
[Gemeinsame]
#permitAll()
Methode, zugänglich für alle Benutzer. -
[Allgemeine]
#denyAll()
Methode, auf die nicht alle Benutzer zugreifen können. -
[Allgemeine]
#authenticated()
Methode, auf die angemeldete Benutzer zugreifen können. -
#anonymous()
Methode, auf die anonyme Benutzer ohne Anmeldung zugreifen können. -
#rememberMe()
Methode, zugänglich für Benutzer, die sich über „An mich erinnern“ anmelden. -
#fullyAuthenticated()
-Methode können Benutzer darauf zugreifen, die nicht angemeldet sind, indem Sie sich an mich erinnern. -
#hasIpAddress(String ipaddressExpression)
Methode, auf die Benutzer über den angegebenen IP-Ausdruck zugreifen können. -
[Allgemein]
#hasRole(String role)
: Benutzer mit der angegebenen Rolle können darauf zugreifen und der Rolle wird ein Präfix vorangestellt“ROLE_”
. -
[Allgemeine]
#hasAnyRole(String... roles)
Methode, auf die Benutzer mit einer beliebigen angegebenen Rolle zugreifen können. -
[Common]
#hasAuthority(String authority)
-Methode,authority
auf die Benutzer mit der angegebenen Berechtigung ( ) zugreifen können. -
[Allgemeine]
#hasAuthority(String... authorities)
Methode, auf die Benutzer mit einer beliebigen angegebenen Berechtigung (authority
) zugreifen können. -
[Beste]
#access(String attribute)
Methode, auf die zugegriffen werden kanntrue
, wenn .
Benutzerdefiniertes 403-Verarbeitungsschema
Bei der Verwendung von Spring Security wird häufig die Meldung 403 (Keine Berechtigung) angezeigt. Spring Security unterstützt die benutzerdefinierte Verarbeitung mit eingeschränkten Berechtigungen, die die AccessDeniedHandler-Schnittstelle implementieren muss
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("Content-Type", "application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
out.flush();
out.close();
}
}
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
{
status: "error",
msg: "权限不足,请联系管理员!",
}
Ausdrucksbasierte Zugriffskontrolle
Die Basisklasse für Zugriffsausdrucksstammobjekte ist SecurityExpressionRoot, die einige allgemeine Ausdrücke bereitstellt, die sowohl in der Web- als auch in der Methodensicherheit verfügbar sind.
https://docs.spring.io/spring-security/site/docs/5.2.7.RELEASE/reference/htmlsingle/#tech-intro-access-control
Die gleiche Funktion wie die zuvor erlernte Berechtigungssteuerung kann über access () erreicht werden.
.antMatchers("/user/login","/login.html").access("permitAll")
.antMatchers("/admin/demo").access("hasAuthority('System')")
Benutzerdefinierte Methode ExceptionTranslationFilter
Authentifizierungsausnahme AuthenticationException
Anmeldeautorisierungsausnahme AccessDeniedException 403accessDeniedHandler.handle(request, response,(AccessDeniedException) exception);
Stellen Sie fest, ob der angemeldete Benutzer berechtigt ist, auf die aktuelle URL zuzugreifen.
Aufruf in Form der beanName.-Methode (Parameter) der Bean im Zugriff:
.anyRequest().access("@mySecurityExpression.hasPermission(request,authentication)")
@Component
public class MySecurityExpression {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
// 获取主体
Object obj = authentication.getPrincipal();
if (obj instanceof UserDetails){
UserDetails userDetails = (UserDetails) obj;
//
String name = request.getParameter("name");
//获取权限
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
//判断name值是否在权限中
return authorities.contains(new SimpleGrantedAuthority(name));
}
return false;
}
}
Methodenautorisierung: Methodenabfang zur Autorisierung MethodSecurityInterceptor
Annotationsbasierte Zugriffskontrolle: Spring Security unterstützt drei Arten von Annotationen für die Methodenzugriffskontrolle
- JSR-250-Anmerkungen
- @Secured-Anmerkung
- Anmerkungen, die Ausdrücke unterstützen
Aktivieren: @EnableGlobalMethodSecurity ist standardmäßig nicht aktiviert
- Serviceschnittstelle oder -methode
- Controller oder Controller-Methode
JSR-250-Anmerkungen角色
- @RolesAllowed gibt die Rollen an, die beim Zugriff auf die entsprechende Methode verfügbar sein sollen
- @PermitAll bedeutet, dass alle Rollen Zugriff haben, d. h. es wird keine Berechtigungskontrolle durchgeführt
- @DenyAll ist das Gegenteil von PermitAll und gibt an, dass auf keine Rolle zugegriffen werden kann
只能定义在方法上
Anmerkung öffnen@EnableGlobalMethodSecurity(jsr250Enabled = true)
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@RolesAllowed({
"ROLE_USER", "ROLE_ADMIN"})
//@PermitAll
@GetMapping("/demo")
public String demo() {
return "spring security demo";
}
@Secured-Anmerkung角色
@Secured wird speziell verwendet, um zu bestimmen, ob es eine Rolle hat und in eine Methode oder Klasse geschrieben werden kann. Parameter sollten ROLE_
mit beginnen.
Anmerkung öffnen@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Secured("ROLE_ADMIN")
@GetMapping("/demo")
public String demo() {
return "spring security demo";
}
Anmerkungen, die Ausdrücke unterstützen权限
In Spring Security sind vier Anmerkungen definiert, die die Verwendung von Ausdrücken unterstützen:
- Bevor die @PreAuthorize-Methode aufgerufen wird
- Nach dem Aufruf der @PostAuthorize-Methode
- Parameterfilterung des Sammlungstyps @PreFilter
- @PostFilter-Rückgabewert zum Filtern
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
- Zugriffskontrolle mit @PreAuthorize
//@PreAuthorize("hasRole('ROLE_ADMIN')")
//@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
//限制只能查询Id小于10的用户
@PreAuthorize("#id<10")
@RequestMapping("/findById")
public User findById(long id) {
User user = new User();
user.setId(id);
return user;
}
// 限制只能查询自己的信息
@PreAuthorize("principal.username.equals(#username)")
@RequestMapping("/findByName")
public User findByName(String username) {
User user = new User();
user.setUsername(username);
return user;
}
//限制只能新增用户名称为abc的用户
@PreAuthorize("#user.username.equals('abc')")
@RequestMapping("/add")
public User add(User user) {
return user;
}
- @PostAuthorize kann nach dem Aufruf der Methode eine Berechtigungsprüfung durchführen
// 在方法find()调用完成后进行权限检查,如果返回值的id是偶数则表示校验通过,否则表示校验失败,将抛出AccessDeniedException
@PostAuthorize("returnObject.id%2==0")
public User find(int id) {
User user = new User();
user.setId(id);
return user;
}
- Filtern mit @PreFilter und @PostFilter
Verwenden Sie @PreFilter und @PostFilter, um Sammlungstypparameter oder Rückgabewerte zu filtern. Bei Verwendung von @PreFilter und @PostFilter entfernt Spring Security Elemente, die dazu führen, dass der entsprechende Ausdruck als falsch ausgewertet wird.
@PostFilter("filterObject.id%2==0")
public List<User> findAll() {
List<User> userList = new ArrayList<User>();
User user;
for (int i=0; i<10; i++) {
user = new User();
user.setId(i);
userList.add(user);
}
return userList;
}
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {
}
Berechtigungsprinzip
Schreiben Sie die Methode #configure (HttpSecurity http) neu, hauptsächlich um die URL-Berechtigungssteuerung zu konfigurieren
Rufen Sie die Methode HttpSecurity#authorizeRequests() auf, um mit der Konfiguration der URL-Berechtigungssteuerung zu beginnen.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 配置请求地址的权限
.authorizeRequests()
.antMatchers("/test/echo").permitAll() // 所有用户可访问
.antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色
.antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
// 任何请求,访问的用户都需要经过认证
.anyRequest().authenticated()
.and()
// 设置 Form 表单登录
//自定义登录页面,可以通过 #loginPage(String loginPage) 设置
.formLogin()
//.loginPage("/login") // 登录 URL 地址
.permitAll() // 所有用户可访问
.and()
// 配置退出相关
.logout()
//.logoutUrl("/logout") // 退出 URL 地址
.permitAll(); // 所有用户可访问
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@RestController
@RequestMapping("/demo")
public class DemoController {
@PermitAll
@GetMapping("/echo")
public String demo() {
return "示例返回";
}
@GetMapping("/home")
public String home() {
return "我是首页";
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/admin")
public String admin() {
return "我是管理员";
}
@PreAuthorize("hasRole('ROLE_NORMAL')")
@GetMapping("/normal")
public String normal() {
return "我是普通用户";
}
}
Autorisierungsprozess. Basierend auf Filter
- Beim Abfangen von Anfragen werden authentifizierte Benutzer, die auf geschützte Webressourcen zugreifen, von Unterklassen von FilterSecurityInterceptor in SecurityFilterChain abgefangen
- Rufen Sie die Ressourcenzugriffsrichtlinie ab. FilterSecurityInterceptor ruft die Sammlung von Berechtigungen ab, die für den Zugriff auf die aktuelle Ressource erforderlich sind, von DefaultFilterInvocationSecurityMetadataSource, einer Unterklasse von SecurityMetadataSource. SecurityMetadataSource ist eigentlich die Abstraktion der Lesezugriffsrichtlinien, und der gelesene Inhalt ist eigentlich die von uns konfigurierte Zugriffsregel
- Schließlich ruft FilterSecurityInterceptor AccessDecisionManager auf, um eine Autorisierungsentscheidung zu treffen. Wenn die Entscheidung getroffen wird, ist der Zugriff auf die Ressource zulässig, andernfalls ist der Zugriff verboten
Basierend auf AOP MethodSecurityInterceptor#invoke
public Object invoke(MethodInvocation mi) throws Throwable {
InterceptorStatusToken token = super.beforeInvocation(mi);
Object result;
try {
result = mi.proceed();
}
finally {
super.finallyInvocation(token);
}
return super.afterInvocation(token, result);
}
entsprechende Schnittstelle
- Der AccessDecisionManager verwendet Abstimmungen, um zu bestimmen, ob auf eine geschützte Ressource zugegriffen werden kann. Eine Reihe von AccessDecisionVotern, die in AccessDecisionManager enthalten sind, werden verwendet, um darüber abzustimmen, ob die Authentifizierung das Recht hat, auf das geschützte Objekt zuzugreifen, und AccessDecisionManager trifft die endgültige Entscheidung basierend auf den Abstimmungsergebnissen.
Implementierungsprinzip von Spring Security
Kernkonfiguration @EnableWebSecurity
https://www.processon.com/view/link/5fa53783637689653d8af2f7
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = {
java.lang.annotation.ElementType.TYPE })
@Documented
@Import({
WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
Filter
SecurityFilterChain
- SecurityContextPersistenceFilter hat zwei Hauptaufgaben: Wenn eine Anfrage eingeht, erstellen Sie
SecurityContext
Sicherheitskontextinformationen und löschen Sie SecurityContextHolder, wenn die Anfrage endet. Der Filter ist für den Kernverarbeitungsfluss verantwortlich, und die Arbeit des Speicherns und Lesens des Sicherheitskontexts wird zur Verarbeitung vollständig dem HttpSessionSecurityContextRepository anvertraut - UsernamePasswordAuthenticationFilter Der Benutzername und das Passwort werden im Formular übermittelt und in Token für eine Reihe von Authentifizierungen gekapselt, die hauptsächlich über diesen Filter abgeschlossen werden. Bei der Formularauthentifizierungsmethode ist dies der kritischste Filter.
- ExceptionTranslationFilter Der Ausnahmeübersetzungsfilter befindet sich hinter der gesamten springSecurityFilterChain und wird zum Übersetzen von Ausnahmen verwendet, die im gesamten Link auftreten. Dieser Filter selbst behandelt keine Ausnahmen, sondern übergibt die Ausnahmen, die während des Authentifizierungsprozesses auftreten, an einige intern verwaltete Klassen zur Verarbeitung. Im Allgemeinen behandelt er zwei Arten von Ausnahmen: AccessDeniedException und AuthenticationException.
- FilterSecurityInterceptor ruft das Authentifizierungsobjekt von SecurityContextHolder ab und vergleicht dann die Berechtigungen des Benutzers mit den von der Ressource benötigten Berechtigungen. Dies ist ein Berechtigungsfilter auf Methodenebene, der im Grunde am Ende der Filterkette steht. Dieser Filter bestimmt die Berechtigungen, die für den Zugriff auf einen bestimmten Pfad verfügbar sein sollten, die Rolle des Benutzers, auf den zugegriffen werden soll, und welche Berechtigungen es gibt. Welche Rollen und Berechtigungen sind für den Zugriffspfad erforderlich? Diese Beurteilungen und Verarbeitungen werden von dieser Klasse durchgeführt.
- HeaderWriterFilter wird verwendet, um einige Header zu HTTP-Antworten hinzuzufügen, z. B. X-Frame-Options, X-XSS-Protection* und X-Content-Type-Options
- CsrfFilter ist ein Filter, der in Spring4 standardmäßig aktiviert ist, um CSRF-Angriffe (Cross-Site Request Forgery) zu verhindern.
- LogoutFilter ist der Filter, der die Abmeldung verarbeitet
- RequestCacheAwareFilter verwaltet intern einen RequestCache zum Zwischenspeichern von Anforderungsanfragen
- SecurityContextHolderAwareRequestFilter: Umschließt ServletRequest einmal, sodass die Anfrage über eine umfangreichere API verfügt
- SessionManagementFilter und sitzungsbezogene Filter verwalten intern eine SessionAuthenticationStrategy. Die Kombination der beiden wird häufig verwendet, um den Schutz vor Sitzungsfixierungsangriffen ( ) zu verhindern
session-fixation protection attack
und die Anzahl mehrerer Sitzungen zu begrenzen, die von demselben Benutzer geöffnet werden - AnonymousAuthenticationFilter Anonymer Identitätsfilter. Um mit nicht protokolliertem Zugriff kompatibel zu sein, hat Spring Security auch eine Reihe von Authentifizierungsprozessen durchlaufen, bei denen es sich lediglich um eine anonyme Identität handelt
Verknüpfung
public abstract class AbstractSecurityWebApplicationInitializer
implements WebApplicationInitializer {
主线源码分析
https://www.processon.com/view/link/5fa3ae25e401fd45d10296e1
Erweitertes Wissen – Spring Authorization Server
Erweitertes Wissen:
Spring verwaltet Spring Security OAuth offiziell nicht mehr und startet offiziell ein Autorisierungsserverprojekt Spring Authorization Server
Spring Authorization Server ist das neueste Autorisierungsserverprojekt, das vom Spring-Team zur Anpassung an das OAuth-Protokoll entwickelt wurde und den ursprünglichen Spring Security OAuth Server ersetzen soll
-
Unterstützt bereits OAuth-Protokolle wie Autorisierungscode, Client, Aktualisierung, Abmeldung usw.
-
Das SAS-Projekt wurde zur Wartung in das offizielle offizielle Lager migriert und ist zu einem offiziellen offiziellen Unterprojekt geworden.
Offizielle Homepage: https://spring.io/projects/spring-authorization-server -
Das Spring-Team gab offiziell bekannt, dass die Wartung von Spring Security OAuth eingestellt wird und das Projekt keiner Iteration unterzogen wird.
-
Als Übergangsversion von SpringBoot 3.0 hat SpringBoot 2.7.0 eine große Anzahl von Konfigurationsklassen zu SpringSecurity abgelaufen. Wenn Sie die alte Version der abgelaufenen Konfiguration verwenden, können Sie kein Upgrade durchführen;
-
Das Spring-Team hatte ursprünglich geplant, nur die Client- und Ressourcenserverfunktionen in OAuth2 bereitzustellen. Die Mehrheit der Menschen forderte jedoch dringend die Bereitstellung der Autorisierungsserver-Funktion, weshalb das Spring-Team ein separates Projekt zur Unterstützung dieser Funktion startete, das den Namen Spring Authorization Server trägt. Offizielle Anleitung: Ankündigung des Spring Authorization Servers
https://docs.spring.io/spring-authorization-server/docs/current/reference/html/getting-started.html#system-requirements