Spring Cloud (Quelle): Spring Cloud Security

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";
    }
}

Fügen Sie hier eine Bildbeschreibung ein

http://localhost:8080/admin/demo

login DefaultLoginPageGeneratingFilter

Anmeldung ist erforderlich, Standardbenutzername: Benutzer, Passwort kann durch Anzeigen des Konsolenprotokolls abgerufen werden
Fügen Sie hier eine Bildbeschreibung ein

Legen Sie Benutzernamen und Passwort fest

UserDetailsServiceAutoConfiguration -> UserDetailsService

public interface UserDetailsService {
    
    
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
  1. speicherbasiert
spring:
  security:
    user:
      name: mendd
      password: 123456
  1. 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"));
    }
}

Fügen Sie hier eine Bildbeschreibung ein

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();
    }
}
  1. 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");
    }
}
  1. 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.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

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);

Fügen Sie hier eine Bildbeschreibung ein

session 过期之后之前的无法登陆了, 正式不建议使用默认的需要自己实现

Fügen Sie hier eine Bildbeschreibung ein
Zur Verwendung muss konfiguriert werdenmaxSessionsPreventsLogin = false

http.sessionManagement().invalidSessionUrl("/session/invalid")
        .maximumSessions(1)
        .expiredSessionStrategy(new MySessionInformationExpiredStrategy())
        .maxSessionsPreventsLogin(false);

Cluster-Sitzung

Fügen Sie hier eine Bildbeschreibung ein

 <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);
}

Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein

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>

Fügen Sie hier eine Bildbeschreibung ein

Konfigurationsklasse ändern

http.csrf().disable(); //关闭csrf防护

Prinzip

Fügen Sie hier eine Bildbeschreibung ein
Wie führt Spring Security die Authentifizierung durch?

  1. Der Benutzername und das Passwort werden vom Filter abgerufen und in die Authentifizierung gekapselt, normalerweise die Implementierungsklasse UsernamePasswordAuthenticationToken
  2. AuthenticationManager Identity Manager ist für die Überprüfung dieser Authentifizierung verantwortlich
  3. 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).
  4. 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
    Fügen Sie hier eine Bildbeschreibung ein
  • Methodenautorisierung: Methodenabfang zur Autorisierung MethodSecurityInterceptor
    Fügen Sie hier eine Bildbeschreibung ein

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 antgestalteten Pfad-Matching-Vorlagen an
ELRequestMatcher Verwenden Sie ELAusdrücke, um Anfragen zuzuordnen
IpAddressMatcher IPMatching-Anfragen basierend auf Adresse, Support IPv4undIPv6
MediaTypeRequestMatcher Basierend auf MediaTypepassenden Anfragen
RegexRequestMatcher Übereinstimmungsanfragen basierend auf regulären Ausdrücken
RequestHeaderRequestMatcher Übereinstimmungsanfragen basierend auf dem Vergleich der Header-Werte
AndRequestMatcher andKombinieren Sie mehrereRequestMatcher
OrRequestMatcher orKombinieren Sie mehrereRequestMatcher
NegatedRequestMatcher nota betreibenRequestMatcher
MvcRequestMatcher Match- Spring MVCAnfrage 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, authorityauf 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 kann true, wenn .

Benutzerdefiniertes 403-Verarbeitungsschema

Fügen Sie hier eine Bildbeschreibung ein

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 {
    
    
  1. 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;
}
  1. @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;
}
  1. 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

Fügen Sie hier eine Bildbeschreibung ein

  1. Beim Abfangen von Anfragen werden authentifizierte Benutzer, die auf geschützte Webressourcen zugreifen, von Unterklassen von FilterSecurityInterceptor in SecurityFilterChain abgefangen
  2. 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
  3. 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;
}

Fügen Sie hier eine Bildbeschreibung ein
Filter
Fügen Sie hier eine Bildbeschreibung ein
SecurityFilterChain

  • SecurityContextPersistenceFilter hat zwei Hauptaufgaben: Wenn eine Anfrage eingeht, erstellen Sie SecurityContextSicherheitskontextinformationen 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 attackund 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 {
    
    

Fügen Sie hier eine Bildbeschreibung ein

主线源码分析

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

Fügen Sie hier eine Bildbeschreibung ein

https://docs.spring.io/spring-authorization-server/docs/current/reference/html/getting-started.html#system-requirements

Guess you like

Origin blog.csdn.net/menxu_work/article/details/127960953