过滤器执行流程图:
~过滤器链
今天增加一个短信验证码的功能,自定义过滤器实现;
1. 首先编写短信验证码的接口,发送短信验证码;
2.写一个过滤器类,继承OncePerRequestFilter,主要用来做验证短信的过滤 ;
3.自定义一个过滤器类,实现AbstractAuthenticationProcessingFilter接口,用来把手机号和验证码封装到token对象,交给认证管理器;
4.自定义一个过滤器类,实现AuthenticationProvider接口,把权限字符串交给security管理;
1. 首先编写短信验证码的接口,发送短信验证码;
@RestController
@Slf4j
public class SmsCodeController {
@RequestMapping("/sms/send/{phone}")
public void getSmsCode(@PathVariable("phone")String phone, HttpServletRequest request){
//发送验证码
String uuid = UUID.randomUUID().toString().substring(0,4);
log.info("验证码是,{}",uuid);
//把验证消息存入session
request.getSession().setAttribute("token",uuid);
}
}
2.写一个过滤器类,继承OncePerRequestFilter,主要用来做验证短信的过滤 ;
@Component
public class SmsCodeCheckFilter extends OncePerRequestFilter {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
//1.判断是不是登录请求
boolean match = antPathMatcher.match("/smslogin", request.getRequestURI());
if(match==false){
filterChain.doFilter(request,response);
return;
}
//2.如果是登录请求,从session里面拿到token
String token = (String)request.getSession().getAttribute("token");
if(!StringUtils.hasLength(token)){
myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("验证码过期了"));
return;
}
//3.再拿到用户输入的code
String code = request.getParameter("code");
if(!StringUtils.hasLength(code)){
myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("验证码不能为空"));
return;
}
String phone = request.getParameter("phone");
if(!StringUtils.hasLength(phone)){
myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("手机号不可为空"));
return;
}
//4.再进行比对,如果正确则直接返回
if(!code.equalsIgnoreCase(token)){
myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("验证码不正确"));
return;
}
//5.如果正确则执行后面的filter
filterChain.doFilter(request,response);
}
}
3.自定义一个过滤器类,实现AbstractAuthenticationProcessingFilter接口,用来把手机号和验证码封装到token对象,交给认证管理器;
public class SmsAuthFilter extends AbstractAuthenticationProcessingFilter {
private boolean postOnly = true;
public SmsAuthFilter() {
super(new AntPathRequestMatcher("/smslogin","POST"));
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//拿到前端输入的手机号和验证码
String phone = request.getParameter("phone");
String code = request.getParameter("code");
if (phone == null) {
phone = "";
}
if (code == null) {
code = "";
}
phone = phone.trim();
//封装token
SMSAuthenticationToken authRequest = new SMSAuthenticationToken(phone,code,null);
setDetails(request,authRequest);
//交给认证管理器
return this.getAuthenticationManager().authenticate(authRequest);
}
protected void setDetails(HttpServletRequest request,
SMSAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
4.自定义一个过滤器类,实现AuthenticationProvider接口,把权限字符串交给security管理;
public class SmsDaoAuthenticationProvider implements AuthenticationProvider {
private MyUserDetailsService myUserDetailsService;
public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) {
this.myUserDetailsService = myUserDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//通过手机号查询权限集合
String phone = (String)authentication.getPrincipal();
UserDetails userDetails = myUserDetailsService.loadUserByUsername(phone);
//再封装到token里面
SMSAuthenticationToken smsAuthenticationToken = new SMSAuthenticationToken(phone,null,userDetails.getAuthorities());
//获取sessionID,为下次访问做个标识
smsAuthenticationToken.setDetails(authentication.getDetails());
return smsAuthenticationToken;
}
//判断是验证码还是账号登录 为true就是短信验证码登录执行以上方法
// 为false就是密码登录执行以上方法
@Override
public boolean supports(Class<?> aClass) {
return (SMSAuthenticationToken.class.isAssignableFrom(aClass));
}
}
自定义过滤器类之后需要用辅助配置类,把过滤器关联起来,然和辅配置类再和主配置类进行关联;
辅助配置类如下:
@Configuration
public class SmsAuthConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private MyAuthenticationSuccessHandler handler;
@Autowired
private MyAuthenticationSuccessHandlerSuccess handlerSuccess;
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private SmsCodeCheckFilter smsCodeCheckFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
//短信验证码登录=========================================================================================start
//添加认证Filter
SmsAuthFilter smsAuthFilter = new SmsAuthFilter();
smsAuthFilter.setAuthenticationSuccessHandler(handlerSuccess);
smsAuthFilter.setAuthenticationFailureHandler(handler);
//设置认证管理器
smsAuthFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
//将MyUserDetailsService通过set方式注入到SmsDaoAuthenticationProvider中
SmsDaoAuthenticationProvider smsDaoAuthenticationProvider = new SmsDaoAuthenticationProvider();
smsDaoAuthenticationProvider.setMyUserDetailsService(myUserDetailsService);
//添加短信认证provider
http.authenticationProvider(smsDaoAuthenticationProvider);
//添加SmsCodeCheckFilterr 到Security过滤器链中
http.addFilterBefore(smsCodeCheckFilter, UsernamePasswordAuthenticationFilter.class);
//添加 smsAuthFilter 到Security过滤器链中
http.addFilterAfter(smsAuthFilter, UsernamePasswordAuthenticationFilter.class);
//短信验证码登录=========================================================================================end
}
}
和主配置类进行关联,用apply方法;
@Autowired
private SmsAuthConfig smsAuthConfig;
http.apply(smsAuthConfig);
SpringSecurity整合JWT;
JWT是什么?
全称为:json web token ,是一个轻巧级的规范;使得再用户与服务之间的信息传递安全可靠;
JWT优点:
1.基于json,方便解析;
2.可令牌中自定义内容,方便解析;
JWT有三部分组成;
1.头部;
2.载荷;
3.签名;
其格式为两点三段式;
整合步骤:
1.导入依赖;
2.登录成功之后携带token;
3.每次请求过来时校验token(自定义一个过滤器类,继承BasicAuthenticationFilter类);
4.把权限集合交给security管理(在主配置类里面的配置 );
1.导入依赖;
<!-- jjwt包 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2.登录成功之后携带token;
@Component
public class MyAuthenticationSuccessHandlerSuccess implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
//1.获取权限集合和用户名称;
Collection<? extends GrantedAuthority> list = authentication.getAuthorities();
String name = authentication.getName();
//2.封装成map返回
HashMap<String, Object> userMap = new HashMap<>();
userMap.put("name",name);
userMap.put("authList",list);
//3.设置私密生成令牌
String path = ResourceUtils.getFile("D:\\java\\rsa.pri").getPath();
try {
PrivateKey key = RsaUtils.getPrivateKey(path);
String tokenExpireInMinutes = JwtUtils.generateTokenExpireInMinutes(userMap, key, 60);
response.getWriter().print(JSONObject.toJSONString(JSONResult.success(tokenExpireInMinutes)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.每次请求过来时校验token(自定义一个过滤器类,继承BasicAuthenticationFilter类);
public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {
public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
//注入成功结果集处理结果
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
public void setMyAuthenticationSuccessHandler(MyAuthenticationSuccessHandler myAuthenticationSuccessHandler) {
this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
//先选择需要放行的路径,不需要token的路径
String[] uri = new String[]{"/smslogin","/login","/sms/send"};
String requestURI = request.getRequestURI();
for(int i=0; i<uri.length; i++){
if(requestURI.contains(uri[i])){
//如果前端传过来的路径包含这些不需要token的路径
chain.doFilter(request,response);
return;
}
}
String token = request.getHeader("token");
if(!StringUtils.hasLength(token)){
myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("没有令牌,请去登录"));
return;
}
//到这里就需要验证token了
try {
SMSAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
e.printStackTrace();
myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("token验证错误!!!"));
return;
}
}
/**
* 解析token,获取用户信息
* @param request
* @return
*/
private SMSAuthenticationToken getAuthentication(HttpServletRequest request) throws Exception {
//获取前端传来的token
String token = request.getHeader("token");
if (token != null) {
//通过token解析出载荷信息
Map map = (Map) JwtUtils.getInfoFromToken(token, RsaUtils.getPublicKey(ResourceUtils.getFile("D:\\Java\\rsa.pub").getPath()),
Map.class);
//取出token中的权限信息
List<Map> auths = (List) map.get("authList");
//定义一个临时的集合(user:add,user:delete)
List auth_temp = new ArrayList();
for (Map auth : auths) {
auth_temp.add(auth.get("authority"));
}
//封装成权限集合
Collection<? extends GrantedAuthority> grantedAuthorities =
AuthorityUtils.commaSeparatedStringToAuthorityList(org.apache.commons.lang3.StringUtils.join(auth_temp,','));
//解析出jwt令牌中携带的用户权限列表,然后将其重写按照security的要求,封装为security能识别的token令牌
return new SMSAuthenticationToken(null, null, grantedAuthorities);
}
return null;
}
}
4.把权限集合交给security管理;(在主配置类里面的配置 )
//校验token的过滤器
MyBasicAuthenticationFilter myBasicAuthenticationFilter = new MyBasicAuthenticationFilter(authenticationManager());
myBasicAuthenticationFilter.setMyAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
http.addFilter(myBasicAuthenticationFilter);