Einführung in Spring Security
Spring Security ist ein leistungsstarkes und hochgradig anpassbares Authentifizierungs- und Zugriffskontroll-Framework. Spring Security konzentriert sich auf die Bereitstellung von Authentifizierungs- und Autorisierungsfunktionen für Java-Anwendungen.
Spring Security hat zwei wichtige Kernfunktionen: Benutzerauthentifizierung (Authentication) und Benutzerautorisierung (Authorization) .
Benutzerauthentifizierung: Überprüfen Sie, ob ein Benutzer ein legitimes Subjekt im System ist, dh ob der Benutzer auf das System zugreifen kann. Die Benutzerauthentifizierung erfordert im Allgemeinen, dass der Benutzer einen Benutzernamen und ein Passwort bereitstellt. Das System schließt den Authentifizierungsprozess ab, indem es den Benutzernamen und das Kennwort überprüft.
Benutzerautorisierung: Stellen Sie sicher, dass ein Benutzer die Berechtigung zum Ausführen einer Operation hat. In einem System haben verschiedene Benutzer unterschiedliche Berechtigungen. Beispielsweise können einige Benutzer eine Datei nur lesen, während einige Benutzer sie sowohl lesen als auch ändern können. Im Allgemeinen weist das System verschiedenen Benutzern unterschiedliche Rollen zu, und jede Rolle entspricht einer Reihe von Berechtigungen.
Vorbereitung
Erstellen Sie ein Spring Boot-Projekt
pom.xml-Datei (eingeführt nach Ihren Bedürfnissen)
<dependencies>
<!-- security(安全认证) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis-plus(数据库操作) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- redis(缓存) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- swagger(api接口文档) -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- jjwt(token生成与校验) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- fastjson2(JSON处理) -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.23</version>
</dependency>
<!-- mysql(连接驱动) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- druid(mysql连接池) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Authentifizierung _
Login-Verifizierungsprozess
Voruntersuchung des Prinzips
Das Prinzip von Spring Security ist eigentlich eine Filterkette, die Filter enthält, die verschiedene Funktionen bereitstellen. Hier können wir uns den Filter im Starter-Beispiel ansehen.
In der Figur sind nur Kernfilter gezeigt, und andere Nicht-Kernfilter sind in der Figur nicht gezeigt.
UsernamePasswordAuthenticationFilter : Verantwortlich für die Verarbeitung der Anmeldeanfrage, nachdem wir den Benutzernamen und das Passwort auf der Anmeldeseite eingegeben haben. Sie ist hauptsächlich für die Zertifizierungsarbeit des Eingangsfalls zuständig.
ExceptionTranslationFilter: Behandelt alle AccessDeniedException und AuthenticationException, die in der Filterkette ausgelöst werden.
FilterSecurityInterceptor: Ein Filter, der für die Berechtigungsüberprüfung verantwortlich ist.
Wir können sehen, welche Filter und ihre Reihenfolge sich in der Spring Security-Filterkette im aktuellen System über Debug befinden.
Klicken Sie auf der Konsole auf Ausdruck auswerten oder Alt+F8, wie unten gezeigt:
Geben Sie dann run.getBean(DefaultSecurityFilterChain.class) ein, um zu filtern, und Sie können 15 Filter im Run-Container sehen:
Spring Security-Konfigurationsklasse
import com.zm.springsecurity.common.filter.CustomAuthenticationFilter;
import com.zm.springsecurity.common.security.CustomAuthenticationFailureHandler;
import com.zm.springsecurity.common.security.CustomAuthenticationSuccessHandler;
import com.zm.springsecurity.common.security.CustomLogoutSuccessHandler;
import com.zm.springsecurity.service.impl.CustomUserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String URL_WHITELIST[] ={
"/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources", "/swagger-resources/configuration/security",
"/swagger-ui.html", "/webjars/**", // swagger不需要授权即可访问的路径
"/login",
"/logout",
"/my/login",
"/my/logout",
"/captcha",
"/password",
"/image/**",
"/test/**"
};
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
protected CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter();
authenticationFilter.setFilterProcessesUrl("/my/login");
authenticationFilter.setUsernameParameter("username");
authenticationFilter.setPasswordParameter("password");
authenticationFilter.setAuthenticationManager(super.authenticationManager());
authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
return authenticationFilter;
}
// @Override
// @Bean
// public AuthenticationManager authenticationManagerBean() throws Exception {
// return super.authenticationManagerBean();
// }
//
// @Override
// protected AuthenticationManager authenticationManager() throws Exception {
// return super.authenticationManager();
// }
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable() // 开启跨域请求和关闭csrf攻击
.userDetailsService(new CustomUserDetailsServiceImpl())
// .formLogin().loginPage("/login_page")
// .loginProcessingUrl("/my/login")
// .usernameParameter("username").passwordParameter("password").permitAll()
// .successHandler(new CustomAuthenticationSuccessHandler()) // 认证成功处理器
// .failureHandler(new CustomAuthenticationFailureHandler()) // 认证失败处理器
// .and()
.logout()
.logoutUrl("/my/logout")
.logoutSuccessHandler(new CustomLogoutSuccessHandler()) // 退出登录成功处理器
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session禁用配置(无状态)
.and()
.authorizeRequests() // 验证请求拦截规则
.antMatchers(URL_WHITELIST).permitAll() // 配置访问认证白名单
.antMatchers("/admin/**").hasRole("admin") // 要具有某种权限
.antMatchers("/user/**").hasAnyRole("admin", "user") // 要具有某种权限中的一种
.anyRequest().authenticated();
http.addFilterAt(this.customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Authentifizierung mit einer Datenbank
Hinweis: Dieser Artikel verwendet MyBatis-Plus als Persistenzschicht-Framework, und der Inhalt, der sich auf MyBatis-Plus bezieht, wird von ihm selbst geschrieben.
Implementieren Sie die UserDetails-Schnittstelle
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class CustomUserDetails implements UserDetails {
private User user;
private List<SimpleGrantedAuthority> authorityList;
public CustomUserDetails() {
}
public CustomUserDetails(User user, List<String> roleList) {
this.user = user;
this.authorityList = roleList.stream()
.map(role -> new SimpleGrantedAuthority(role))
.collect(Collectors.toList());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorityList;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getPassword();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.getStatus();
}
}
Benutzerdefinierter UsernamePasswordAuthenticationFilter-Filter
Wenn es sich bei der Verarbeitungsanforderung um Daten vom Typ Formular handelt, ignoriert und löscht dieser Schritt den Inhalt, der sich auf CustomAuthenticationFilter in der Sicherheitskonfigurationsklasse bezieht. UsernamePasswordAuthenticationFilter ist ein Authentifizierungsfilter. Standardmäßig kann er nur die vom Formular übermittelten Daten verarbeiten. Wenn Sie JSON-Daten verarbeiten müssen, müssen Sie die Methode tryAuthentication() von UsernamePasswordAuthenticationFilter überschreiben.
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* 登录认证过滤器,处理认证的请求体为 JSON 的数据
*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String contentType = request.getContentType();
logger.info("contentType = " + contentType);
if (contentType.equals(MediaType.APPLICATION_JSON_VALUE) || contentType.equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream inputStream = request.getInputStream()) {
ObjectMapper mapper = new ObjectMapper(); // JSON数据映射器
Map<String,String> params = mapper.readValue(inputStream, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(params.get("username"), params.get("password"));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken("", "");
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
benutzerdefinierter Prozessor
JWT-Tool-Klasse
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;
import java.util.Date;
public class JWTUtils {
private static final String tokenSignKey = "zm_sign_key"; // 私钥(盐),太短会报异常:secret key byte array cannot be null or empty.
private static final Integer tokenExpiration = 60 * 60 * 24 * 14; // 14天
public static String createToken(String username){
String token = Jwts.builder()
.setSubject("AUTH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("username", username)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compact();
return token;
}
public static String createToken(Long userId, String username){
String token = Jwts.builder()
.setSubject("AUTH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("username", username)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compact();
return token;
}
public static Long getUserId(String token) {
try {
if (!StringUtils.hasLength(token)) {
return null;
}
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims.get("userId", Long.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getUsername(String token) {
try {
if (!StringUtils.hasLength(token)) {
return "";
}
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims.get("username", String.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Antworten Sie auf JSON-Dateninformationen
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ResponseUtils {
public static void response(HttpServletResponse response, String data) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter responseWriter = response.getWriter();
responseWriter.write(data);
responseWriter.flush();
responseWriter.close();
}
}
Benutzerdefinierter Authentifizierungs-Erfolgshandler
import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.JWTUtils;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String username = authentication.getPrincipal().toString();
String token = JWTUtils.createToken(username);
String jsonString = JSON.toJSONString(ResultUtils.ok("登录成功", token));
ResponseUtils.response(response, jsonString);
}
}
Benutzerdefinierter Handler für Authentifizierungsfehler
import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String message = exception.getMessage();
if(exception instanceof BadCredentialsException){
message = "用户名或密码错误!";
}
String jsonString = JSON.toJSONString(ResultUtils.fail(message));
ResponseUtils.response(response, jsonString);
}
}
Benutzerdefinierter Logout-Erfolgshandler
import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String jsonString = JSON.toJSONString(ResultUtils.ok("退出登录成功!"));
ResponseUtils.response(response, jsonString);
}
}
Autorisierung _ _