引用依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.4.2</version>
</dependency>
SecurityConfig
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig {
private final CustomAuthenticationProvider customAuthenticationProvider;
private final CustomUserDetailsService customUserDetailsService;
public SecurityConfig(CustomAuthenticationProvider customAuthenticationProvider, CustomUserDetailsService customUserDetailsService) {
this.customAuthenticationProvider = customAuthenticationProvider;
this.customUserDetailsService = customUserDetailsService;
log.info("初始化 Security 配置");
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
CustomUserDetailsService customUserDetailsService,
RegisteredClientRepository registeredClientRepository) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.userDetailsService(customUserDetailsService);
http.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
http.formLogin(form -> form
.loginPage("/auth/login")
.loginProcessingUrl("/server-login")
.permitAll()
);
http.addFilterAt(customUsernamePasswordAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() {
CustomUsernamePasswordAuthenticationFilter filter = new CustomUsernamePasswordAuthenticationFilter();
filter.setFilterProcessesUrl("/server-login");
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(customAuthenticationProvider);
}
@Bean
@Order(1)
public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers(new AntPathRequestMatcher("/userInfo")).authenticated()
)
.csrf(csrf -> csrf.disable())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
AuthorizationServerConfig
@Configuration
public class AuthorizationServerConfig {
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("20210903")
.clientSecret("{noop}cas123456")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8082/client/callbackCode")
.scope("openid")
.scope("message:read")
.scope("message:write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
.build();
return new InMemoryRegisteredClientRepository(messagingClient);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.issuer("http://127.0.0.1:8080")
.build();
}
}
自定义认证功能
@Component
@Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final LoginService loginService;
public CustomAuthenticationProvider(LoginService loginService) {
this.loginService = loginService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
CustomAuthenticationToken customAuthRequest = (CustomAuthenticationToken) authentication;
String username = customAuthRequest.getName();
String password = StrUtil.toString(customAuthRequest.getCredentials());
String code = customAuthRequest.getCode();
String verifyToken = customAuthRequest.getVerifyToken();
log.info("CustomAuthenticationProvider authenticate username: {}, password: {}, code: {}, verifyToken: {}", username, password, code, verifyToken);
return new CustomAuthenticationToken(username, password, java.util.Collections.emptyList(), code, verifyToken);
}
@Override
public boolean supports(Class<?> authentication) {
return CustomAuthenticationToken.class.isAssignableFrom(authentication);
}
}
自定义认证令牌
@EqualsAndHashCode(callSuper = true)
@Data
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {
private final String code;
private final String verifyToken;
public CustomAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities,
String code, String verifyToken) {
super(principal, credentials, authorities);
this.code = code;
this.verifyToken = verifyToken;
}
}
自定义拦截器
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final String code = "code";
private final String verifyToken = "verifyToken";
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
String code = request.getParameter(this.code);
String verifyToken = request.getParameter(this.verifyToken);
CustomAuthenticationToken token = new CustomAuthenticationToken(username, password, CollUtil.newArrayList(), code, verifyToken);
setDetails(request, token);
return this.getAuthenticationManager().authenticate(token);
}
}
自定义登录页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 350px;
}
.login-container h2 {
text-align: center;
margin-bottom: 20px;
color: #333;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.btn-login {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn-login:hover {
background-color: #0069d9;
}
.error-message {
color: red;
text-align: center;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="login-container">
<h2>用户登录</h2>
<div class="error-message" id="errorMessage" style="display:none;">
用户名或密码错误,请重试。
</div>
<form action="/server-login" method="post">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required />
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required />
</div>
<div class="form-group">
<label for="phone">手机号</label>
<input type="text" id="phone" name="phone" placeholder="可选" />
</div>
<div class="form-group">
<label for="code">验证码</label>
<input type="text" id="code" name="code" placeholder="请输入验证码" required />
</div>
<input type="hidden" id="verifyToken" name="verifyToken" value="YOUR_VERIFY_TOKEN_HERE" />
<button type="submit" class="btn-login">登录</button>
</form>
</div>
<script>
const urlParams = new URLSearchParams(window.location.search);
const tokenFromUrl = urlParams.get('verifyToken');
if (tokenFromUrl) {
document.getElementById('verifyToken').value = tokenFromUrl;
}
if (urlParams.has('error')) {
document.getElementById('errorMessage').style.display = 'block';
}
</script>
</body>
</html>