springboot security 安全机制
springboot security 安全机制
认证流程:
1、由认证配置WebSecurityConfigurerAdapter的configure(HttpSecurity http)方法进入,添加拦截器addFilterBefore
2、进入拦截器AbstractAuthenticationProcessingFilter的attemptAuthentication方法,指定认证对象AbstractAuthenticationToken
3、进入认证逻辑AuthenticationProvider,根据supports的断定对认证的目标对象指定对哪个拦截器进行认证,进入具体的认证逻辑方法authenticate
4、认证结果:认证成功后进入拦截器的successfulAuthentication方法;认证失败后进入拦截器的unsuccessfulAuthentication方法
5、对认证结果进行处理
a.认证成功的逻辑:进入SimpleUrlAuthenticationSuccessHandler的onAuthenticationSuccess方法
b.认证失败的逻辑:进入SimpleUrlAuthenticationFailureHandler的onAuthenticationFailure方法
6、返回结果给页面,将数据封装在ObjectMapper对象中,将会以文本形式返回给客户端(页面)
代码块
自定义认证配置,实现WebSecurityConfigurerAdapter
package com.travelsky.config;
import com.travelsky.auto.login.BeforeLoginFilter;
import com.travelsky.auto.login.LoginProvider;
import com.travelsky.auto.login.OnFailureLogin;
import com.travelsky.auto.token.OnFailureToken;
import com.travelsky.auto.token.TokenFilter;
import com.travelsky.auto.token.TokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 认证配置
*/
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
/**
* 认证成功处理
*/
@Autowired
private SimpleUrlAuthenticationSuccessHandler successHandler;
/**
* 登录认证失败后处理
*/
@Autowired
private OnFailureLogin failureHandler;
/**
* token认证失败后处理
*/
@Autowired
private OnFailureToken onFailureToken;
/**
* 登录认证逻辑
*/
@Autowired
private LoginProvider loginProvider;
/**
* token认证逻辑
*/
@Autowired
private TokenProvider tokenProvider;
/**
* 配置请求权限
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 允许登录路径通过
.antMatchers("/login").permitAll()
// 允许业务路径通过
.antMatchers("/**").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
// 添加拦截器
.addFilterBefore(getLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(getTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
/**
* 登录拦截器
* @return
* @throws Exception
*/
private AbstractAuthenticationProcessingFilter getLoginFilter() throws Exception {
BeforeLoginFilter beforeLoginFilter = new BeforeLoginFilter("/login", successHandler, failureHandler);
beforeLoginFilter.setAuthenticationManager(super.authenticationManager());
return beforeLoginFilter;
}
/**
* token拦截器
* @return
* @throws Exception
*/
private AbstractAuthenticationProcessingFilter getTokenFilter() throws Exception {
TokenFilter tokenFilter = new TokenFilter("/**", onFailureToken);
tokenFilter.setAuthenticationManager(super.authenticationManager());
return tokenFilter;
}
/**
* 配置认证逻辑列表,按顺序进行认证
* @param auth
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(loginProvider);
auth.authenticationProvider(tokenProvider);
}
}
自定义拦截器,继承AbstractAuthenticationProcessingFilter
package com.travelsky.auto.login;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录自定义拦截器,继承AbstractAuthenticationProcessingFilter
*/
@Slf4j
public class BeforeLoginFilter extends AbstractAuthenticationProcessingFilter {
/**
*认证成功的处理对象
*/
private AuthenticationSuccessHandler successHandler;
/**
* 认证失败的处理对象
*/
private AuthenticationFailureHandler failureHandler;
public BeforeLoginFilter(String defaultFilterProcessesUrl, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) {
super(defaultFilterProcessesUrl);
this.successHandler = successHandler;
this.failureHandler = failureHandler;
}
/**
* 过滤器处理逻辑
* @param request
* @param response
* @return
* @throws AuthenticationException
* @throws IOException
* @throws ServletException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
log.info("进入登录过滤器");
String userName = request.getParameter("userName");
String password = request.getParameter("password");
LoginInfo loginInfo = new LoginInfo(null, userName, password);
log.info("username = {}", userName);
log.info("password = {}", password);
// 返回指定的认证对象
LoginAuthenticationToken loginAuthentication = new LoginAuthenticationToken(null, loginInfo, null);
// 将来由认证逻辑AuthenticationProvider来处理,这里返回LoginAuthenticationToken对象,将来由AuthenticationProvider的supports方法相匹配的认证逻辑来处理
return this.getAuthenticationManager().authenticate(loginAuthentication);
}
/**
* 认证逻辑认证成功成功后会调用此方法
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
successHandler.onAuthenticationSuccess(request, response, authResult);
}
/**
* 认证逻辑认证失败后调用此方法
* @param request
* @param response
* @param failed
* @throws IOException
* @throws ServletException
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
failureHandler.onAuthenticationFailure(request, response, failed);
}
}
自定义认证逻辑,继承AuthenticationProvider
package com.travelsky.auto.login;
import com.alibaba.fastjson.JSONObject;
import com.travelsky.pojo.MenuVO;
import com.travelsky.pojo.SysUserInfo;
import com.travelsky.service.SysUserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 认证逻辑
*/
@Component
@Slf4j
public class LoginProvider implements AuthenticationProvider {
@Autowired
private SysUserInfoService userInfoService;
/**
* 认证逻辑,认证的目标对象由supports断定,supports断定的目标对象是LoginAuthenticationToken自定义认证对象
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.info("登录验证器,接收参数:{}", JSONObject.toJSONString(authentication));
// Authentication的具体子类,可由supports断定,supports断定子类是LoginAuthenticationToken,因此可以强转
final LoginAuthenticationToken loginAuthentication = (LoginAuthenticationToken) authentication;
final LoginInfo loginInfo = loginAuthentication.getLoginInfo();
final String userName = loginInfo.getUserName();
final SysUserInfo userInfo = userInfoService.getByUserId(userName);
if (userInfo.getPassword().equals(loginInfo.getPassword())) {
final List<MenuVO> userInfoMenus = userInfoService.getUserInfoMenus(userInfo.getId());
return new LoginAuthenticationToken(null, loginInfo, userInfoMenus);
} else {
log.error("登录验证失败");
throw new AuthenticationServiceException("登录失败.........");
}
}
/**
* 指定对谁进行认证,这里指定对LoginAuthenticationToken自定义认证对象进行认证
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
return LoginAuthenticationToken.class.isAssignableFrom(authentication);
}
}
自定义认证成功的处理逻辑,继承SimpleUrlAuthenticationSuccessHandler
package com.travelsky.auto.login;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.travelsky.auto.token.TokenContent;
import com.travelsky.auto.token.TokenFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义认证成功处理逻辑,继承SimpleUrlAuthenticationSuccessHandler类
*/
@Component
@Slf4j
public class OnSuccessfulLogin extends SimpleUrlAuthenticationSuccessHandler {
/**
* 返回页面的对象,可将对象转化为文本形式(json格式)
*/
@Autowired
private ObjectMapper objectMapper;
/**
* token工厂
*/
@Autowired
private TokenFactory tokenFactory;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("登录认证成功,传入参数:{}", JSONObject.toJSONString(authentication));
// 配置响应编码格式为UTF-8
response.setCharacterEncoding("UTF-8");
// 配置返回格式为json
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
// Authentication的具体子类为LoginAuthenticationToken
final LoginAuthenticationToken loginAuthenticationToken = (LoginAuthenticationToken) authentication;
final TokenContent tokenContent = tokenFactory.createToken("token");
JSONObject json = new JSONObject();
json.put("code", "000000");
json.put("message", "认证成功");
json.put("claims", tokenContent.getClaims());
json.put("token", tokenContent.getToken());
json.put("menuList", loginAuthenticationToken.getUserInfoMenus());
log.info("生产token:{}",tokenContent.getToken());
// 将数据保存到objectMapper中,将会转化为文本格式,返回给页面
objectMapper.writeValue(response.getWriter(), json);
}
}
自定义认证失败逻辑,继承SimpleUrlAuthenticationFailureHandler
package com.travelsky.auto.login;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class OnFailureLogin extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("登录认证失败,传入参数:{}", JSONObject.toJSONString(exception));
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
JSONObject json = new JSONObject();
json.put("code", "999999");
json.put("message", exception.getMessage());
objectMapper.writeValue(response.getWriter(), json);
}
}
自定义认证对象,继承AbstractAuthenticationToken
package com.travelsky.auto.login;
import com.travelsky.pojo.MenuVO;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import java.util.List;
/**
* 自定义登录认证对象
*/
public class LoginAuthenticationToken extends AbstractAuthenticationToken {
/**
* 登录数据对象
*/
private LoginInfo loginInfo;
/**
* 菜单数据对象
*/
private List<MenuVO> userInfoMenus;
LoginAuthenticationToken(Collection<? extends GrantedAuthority> authorities, LoginInfo loginInfo, List<MenuVO> userInfoMenus) {
super(authorities);
this.userInfoMenus = userInfoMenus;
this.loginInfo = loginInfo;
}
@Override
public Object getCredentials() {
return loginInfo.getPassword();
}
@Override
public Object getPrincipal() {
return loginInfo.getUserName();
}
public LoginInfo getLoginInfo() {
return loginInfo;
}
public List<MenuVO> getUserInfoMenus() {
return userInfoMenus;
}
}
自定义拦截器:继承AbstractAuthenticationProcessingFilter,重写attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 方法,返回AbstractAuthenticationToken的子类,AbstractAuthenticationToken的子类可自己定义内容(例如:用户信息,token字符串,Claims),只要继承AbstractAuthenticationToken即可
自定义验证规则:实现AuthenticationProvider接口,authenticate方法参数Authentication与拦截器attemptAuthentication方法返回的是同一个对象,重写 authenticate(Authentication authentication),其中supports(Class<?> authentication)方法用来指定当前检验规则是针对哪个AbstractAuthenticationToken子类对象。springboot自带默认的登录验证是返回UsernamePasswordAuthenticationToken对象。