SpringSecurity 自定义扩展
1、定义filter
import com.lzy.wzy.token.MyUsernamePasswordToken;
import com.lzy.wzy.utils.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final Logger log= LoggerFactory.getLogger(MyUsernamePasswordAuthenticationFilter.class);
private String usernameParameter = "username";
private String passwordParameter = "password";
private SessionRegistry sessionRegistry;
private boolean postOnly = true;
public MyUsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/api/doLogin", "POST"));
}
public MyUsernamePasswordAuthenticationFilter(SessionRegistry sessionRegistry){
super(new AntPathRequestMatcher("/api/doLogin", "POST"));
this.sessionRegistry=sessionRegistry;
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
log.info("IpUtil.getIpAddr(request) {}",IpUtil.getIpAddr(request));
MyUsernamePasswordToken authRequest = new MyUsernamePasswordToken(username, password);
sessionRegistry.registerNewSession(request.getSession().getId(),authRequest.getPrincipal());
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
protected void setDetails(HttpServletRequest request, MyUsernamePasswordToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return this.usernameParameter;
}
public final String getPasswordParameter() {
return this.passwordParameter;
}
}
2、定义所需的AuthenticationToken
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Collection;
public class MyUsernamePasswordToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 540L;
private final Object principal;
private Object credentials;
private String uri;
public MyUsernamePasswordToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public MyUsernamePasswordToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public MyUsernamePasswordToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities,String uri) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.uri=uri;
super.setAuthenticated(true);
}
public String getUri() {
return uri;
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
3、定义provider
import com.lzy.wzy.config.MyPasswordEncoder;
import com.lzy.wzy.exception.DepartmentSelectionException;
import com.lzy.wzy.model.UserBean;
import com.lzy.wzy.service.imp.UserServiceImp;
import com.lzy.wzy.token.MyUsernamePasswordToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserServiceImp userServiceImp;
@Autowired
private MyPasswordEncoder myPasswordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserBean user = (UserBean) userServiceImp.loadUserByUsername(username);
if (!user.isEnabled()) {
throw new DisabledException("该账户已被禁用,请联系管理员");
} else if (!user.isAccountNonLocked()) {
throw new LockedException("该账号已被锁定");
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("该账号已过期,请联系管理员");
} else if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录");
}
// if (!checkUtils.isPasswordOk(password)) {
// throw new BadCredentialsException("密码格式有误!");
// }
//验证密码
if (!myPasswordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("输入密码错误!");
}
return new MyUsernamePasswordToken(user, password, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
// 判断 authentication 是不是 AuthenticationToken 的子类或子接口
return MyUsernamePasswordToken.class.isAssignableFrom(authentication);
}
}
4、定义jwt转换器,以及token生成配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.annotation.Resource;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class JwtTokenAuthConfig {
@Autowired
private MyTokenEnhancer myTokenEnhancer;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean(name = "jwtTokenStoreAuth")
public TokenStore jwtTokenStoreAuth(){
return new JwtTokenStore(jwtAccessTokenConverterAuth());
}
@Bean("redisTokenStore")
public RedisTokenStore redisTokenStore(RedisConnectionFactory redisConnectionFactory){
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
redisTokenStore.setPrefix("auth_token:");
return redisTokenStore;
}
@Bean(name = "jwtAccessTokenConverterAuth")
public JwtAccessTokenConverter jwtAccessTokenConverterAuth(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
DefaultUserAuthenticationConverter userAuthenticationConverter = new DefaultUserAuthenticationConverter();
userAuthenticationConverter.setUserDetailsService(userDetailsService);
defaultAccessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
jwtAccessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
//jwtAccessTokenConverter.setSigningKey("wylwzy");
ClassPathResource resource = new ClassPathResource("oauth-jwt.jks");
KeyStoreKeyFactory keyFactory = new KeyStoreKeyFactory(resource, "wzy666".toCharArray());
KeyPair keyPair = keyFactory.getKeyPair("wzy");
jwtAccessTokenConverter.setKeyPair(keyPair);
return jwtAccessTokenConverter;
}
/**
* 配置令牌管理
*/
@Bean
public AuthorizationServerTokenServices authorizationServerTokenServices(ClientDetailsService clientDetailsService) {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);
service.setSupportRefreshToken(true);
service.setReuseRefreshToken(true);
service.setTokenStore(jwtTokenStoreAuth());
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> list=new ArrayList<>();
list.add(myTokenEnhancer);
list.add(jwtAccessTokenConverterAuth());
tokenEnhancerChain.setTokenEnhancers(list);
service.setTokenEnhancer(tokenEnhancerChain);
return service;
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
@Configuration
@Slf4j
public class JwtTokenReConfig {
@Bean(name = "jwtTokenStoreRe")
public TokenStore jwtTokenStoreRe() {
return new JwtTokenStore(jwtAccessTokenConverterRe());
}
@Bean(name = "jwtAccessTokenConverterRe")
public JwtAccessTokenConverter jwtAccessTokenConverterRe() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//jwtAccessTokenConverter.setSigningKey("wylwzy");
jwtAccessTokenConverter.setVerifierKey(getPublicKey());
return jwtAccessTokenConverter;
}
private String getPublicKey() {
ClassPathResource resource = new ClassPathResource("publickey.txt");
try (InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader)) {
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
}
5、定义认证成功handler
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lzy.wzy.mapper.UserMapper;
import com.lzy.wzy.model.UserBean;
import com.lzy.wzy.result.Response;
import com.lzy.wzy.result.ResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final static String CLIENT_ID="clientapp";
private final static String CLIENT_SECRET="$2a$10$nAwPWghTj2lTSG1yT2MXJu8zbIHW23UyG9wh5KGROnztQqFnJpvLS";
private final static String GRANT_TYPE="password";
private ClientDetails clientDetails;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private UserMapper userMapper;
@Autowired
private ObjectMapper objectMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("登录成功");
if (null == clientDetails) {
log.info("查询 client info");
clientDetails = clientDetailsService.loadClientByClientId(CLIENT_ID);
if (null == clientDetails) {
throw new UnapprovedClientAuthenticationException("clientId不存在" + CLIENT_ID);
}
}
if (!CLIENT_SECRET.equals(clientDetails.getClientSecret())){
throw new UnapprovedClientAuthenticationException("clientSecret不匹配" + clientDetails.getClientSecret());
}else {
Map<String, String> map=new HashMap<>();
UserBean user = (UserBean) authentication.getPrincipal();
map.put("username",user.getUsername());
map.put("password",user.getPassword());
//更新最后一次登录时间
Map<String,Object> userMap=new HashMap<>();
userMap.put("userUuid",user.getUserUuid());
userMap.put("timeOnline", LocalDateTime.now());
userMapper.updateOnlineTime(userMap);
TokenRequest tokenRequest = new TokenRequest(map,CLIENT_ID,clientDetails.getScope(),GRANT_TYPE);
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
// System.out.println(accessToken.getValue());
String token = authorizationServerTokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), tokenRequest).getValue();
System.out.println(token);
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(objectMapper.writeValueAsString(new Response<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMsg(), accessToken.getValue())));
writer.flush();
}
}
}
6、将自定义的配置加入到springsecurity的配置
import com.lzy.wzy.filter.MyUsernamePasswordAuthenticationFilter;
import com.lzy.wzy.handle.CustomAuthenticationFailureHandler;
import com.lzy.wzy.handle.CustomAuthenticationSuccessHandler;
import com.lzy.wzy.provider.MyAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;
@Component
public class MyAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private MyAuthenticationProvider myAuthenticationProvider;
@Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
@Autowired
private SessionRegistry sessionRegistry;
@Autowired
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
@Override
public void configure(HttpSecurity http) throws Exception {
MyUsernamePasswordAuthenticationFilter authenticationFilter = new MyUsernamePasswordAuthenticationFilter(sessionRegistry);
authenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
authenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
authenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
authenticationFilter.setSessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry));
http.addFilterBefore(new ConcurrentSessionFilter(sessionRegistry, sessionInformationExpiredStrategy), ConcurrentSessionFilter.class);
http.authenticationProvider(myAuthenticationProvider).addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement()//只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
.invalidSessionUrl("/index/none/toLogin")
.maximumSessions(1)
.sessionRegistry(sessionRegistry)
.expiredUrl("/index/none/toLogin");
}
}
7、将配置应用WebSecurity中
@Override
protected void configure(HttpSecurity http) throws Exception {
http.apply(myAuthenticationSecurityConfig);
http.cors();
http.headers().frameOptions().disable();
http.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
//允许匿名访问所有接口
http.csrf().disable();
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/index/none/toLogin")
.permitAll()
.and()
.logout()
.logoutSuccessHandler(customLogoutSuccessHandler)
.permitAll();
}
8、tokenenhancer token增强器
import com.lzy.wzy.model.UserBean;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class MyTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String,Object> map=new HashMap<>();
UserBean userBean = (UserBean)oAuth2Authentication.getUserAuthentication().getPrincipal();
map.put("username",userBean.getUsername());
map.put("userUuid",userBean.getUserUuid());
map.put("lzy",userBean.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
return oAuth2AccessToken;
}
}
OAuth2AccessToken oAuth2AccessToken;
try {
oAuth2AccessToken = jwtTokenStoreRe.readAccessToken(token);
if (oAuth2AccessToken.isExpired()) {
log.info("token 已过期");
String refresh = request.getHeader("refresh");
OAuth2RefreshToken oAuth2RefreshToken = jwtTokenStoreRe.readRefreshToken(refresh);
System.out.println(jwtTokenStoreRe.readAccessToken(oAuth2RefreshToken.getValue()));
expiredTokenMono(response);
return;
}
Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
对于bms认证头生成方法是Base64.getEncoder().encodeToString(“xx:xx”.getBytes())
https://blog.csdn.net/lucktomcat/article/details/125555678