gradle引入
implementation 'org.springframework.boot:spring-boot-starter-security'
WebSecurityConfiguration 拦截请求鉴权
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import javax.annotation.Resource;
@Slf4j
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private XiaozuAuthenticationUserDetailsService authenticationUserDetailsService;
@Resource
private UserInfoService userInfoService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//对/api下所有请求拦截鉴权
http.antMatcher(CommonConstant.HttpPathNamespace.API+"/**")
.csrf().disable()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于获取token的rest api要允许匿名访问
// .antMatchers(CommonConstant.HttpPathNamespace.INNER+"/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new Http203AuthenticationEntryPoint());
http.addFilterAfter(new XiaozuAuthCodeAuthenticationFilter(
userInfoService), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
http.headers().cacheControl();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(authenticationUserDetailsService);
auth.authenticationProvider(provider);
}
@Bean
public XiaozuAuthenticationUserDetailsService authenticationUserDetailsService() {
return new XiaozuAuthenticationUserDetailsService();
}
}
XiaozuAuthCodeAuthenticationFilter处理鉴权逻辑
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
@Slf4j
public class XiaozuAuthCodeAuthenticationFilter extends OncePerRequestFilter {
private UserInfoService userInfoService;
public XiaozuAuthCodeAuthenticationFilter() {
}
public XiaozuAuthCodeAuthenticationFilter(UserInfoService userInfoService) {
this.userInfoService = userInfoService;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
//1、用户首次访问,客户端没有authcode(后端作为token进行存储redis),则返回203,表示没有授权头
//2、若是支付宝或者微信,则客户端请求支付宝或者微信api生成authCode;若不是,则走另外一套鉴权逻辑了(鉴权成功后,token在后端生成返回给前端)
//3、有authCode(token)信息后,则查询是否存在该token,若存在,则鉴权通过;若不存在,则进行三方鉴权,然后处理用户相关信息
String appName = getAppName(request);
if (StringUtils.isBlank(appName)) {
authFailResp(response);
log.error("用户鉴权失败appName为空:{}",request);
return;
}
EnumAppName enumAppName = EnumAppName.valueOf(appName);
String authCode = getAuthCode(request, enumAppName);
if (!ObjectUtils.isEmpty(authCode)) {
String username = StringUtils.EMPTY;
if (!userCacheService.checkAuthToken(authCode,appName)) {
// token在redis中不存在,则需要进行存储
// 1、使用authCode进行鉴权,通过第三方获取用户信息
// 2、鉴权通过后,拿到用户信息,则进行用户处理(在MySQL中判断用户是否存在,若存在,则返回useId,若不存在,则生成user)
username = dealWithAuth(enumAppName, channel, authCode, appName);
if (StringUtils.isBlank(username)) {
authFailResp(response);
log.error("用户鉴权失败appName:{},channel:{}", appName, channel);
return;
}
// 3、将用户token,userId存储在redis中
authCode = DigestUtils.md5DigestAsHex(username.getBytes());
userCacheService.setAuthToken(authCode, username,appName);
} else {
username = userCacheService.getUserName(authCode,appName);
}
// security容器中放入用户信息
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(username, authCode);
SecurityContextHolder.getContext().setAuthentication(authentication);
response.setHeader(CommonConstant.HttpHeader.TOKEN, authCode);
}
filterChain.doFilter(request, response);
}
private void authFailResp(HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
PrintWriter out = response.getWriter();
JSONObject res = new JSONObject();
res.put("msg", OperationStatus.AUTH_FAIL.getName());
res.put("code", OperationStatus.AUTH_FAIL.getType());
out.append(res.toString());
}
private String dealWithAuth() {
//处理鉴权逻辑 返回用户唯一标识token
return "";
}
}
}
XiaozuAuthenticationUserDetailsService
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Component
public class XiaozuAuthenticationUserDetailsService implements
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
@Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException {
String username = (String) token.getPrincipal();
String accessToken = (String) token.getCredentials();
return new User(username, accessToken, Collections.emptyList());
}
}
Http203AuthenticationEntryPoint
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Http203AuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value(), authException.getMessage());
}
}