主要实现
主要通过过滤器实现,通过一层层拦截来实现登录认证等操作。主要讲一下UsernamePasswordAuthenticationFilter
和BasicAuthenticationFilter
的实现
// 启动springboot的时候,控制台打印的日志。都是默认的过滤器实现
2019-12-05 18:36:23.748 INFO 10472 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1115433e,
org.springframework.security.web.context.SecurityContextPersistenceFilter@21ba2445,
org.springframework.security.web.header.HeaderWriterFilter@257e0827,
org.springframework.web.filter.CorsFilter@4fdca00a,
org.springframework.security.web.authentication.logout.LogoutFilter@7fb48179,
co.jratil.springsecuritydemo.filter.LoginAuthenticationFilter@513b52af,
co.jratil.springsecuritydemo.filter.JwtAuthorizationFilter@5a8c93,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@69d23296,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5434e40c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3bed3315,
org.springframework.security.web.session.SessionManagementFilter@22752544,
org.springframework.security.web.access.ExceptionTranslationFilter@78b612c6,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2d55e826]
简单的demo,主要通过
- 继承
UsernamePasswordAuthenticationFilter
来实现账号密码的验证- 继承
BasicAuthenticationFilter
来实现授权的问题,比如是否登录,和获取用户的权限放入全局的SecurityContext
中- 需要自定义继承
UserDetails
和UserDetailsSevice
两个接口,来覆盖默认的实现,从而从数据库获取到所需的用户和用户信息
1. 继承UsernamePasswordAuthenticationFilter
过滤器实现
主要实现过程:
- 先通过过滤器中的
attemptAuthentication()
方法,把request中的登录的账号密码取出来。- 然后通过
AuthenticationManager
的authenticate()
方法来认证,其中默认是通过ProviderManager
来实现该方法- 在
ProviderManager
中,会循环获取到所有可以处理该认证的provider,再调用其authentication()
方法来认证,默认有个AbstractUserDetailsAuthenticationProvider
实现AbstractUserDetailsAuthenticationProvider
中有一个retrieveUser()的虚方法,默认通过DaoAuthenticationProvider
来实现- 在
DaoAuthenticationProvider
中会获取到自定义的UserDetrailsService
的实现类,通过调用该实现类中的loadUserByUsername()
来获取到UserDetails
的对象。- 最终该对象会放进一个
UsernamePasswordAuthenticationoken
对象中。- 在认证成功后会调用
successfulAuthentication()
方法,在里面将token放入header中。返回给前端- 失败则调用
unsuccessfulAuthentication()
方法,将错误返回
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(LoginAuthenticationFilter.class);
private AuthenticationManager authenticationManager;
private boolean rememberMe = false;
// 通过构造函数获取AuthenticationManager,最后主要通过该对象的authenticate来实现认证
public LoginAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/auth/login");
}
// 重写方法,过滤器的主要实现
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 从request中获取json数据,就是body中的数据,前端请求传入一个LoginUser的Json
LoginUser loginUser = objectMapper.readValue(request.getInputStream(), LoginUser.class);
log.info(loginUser.toString());
this.rememberMe = loginUser.isRememberMe();
// 设置一个authentication获取账号密码用来验证
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
loginUser.getUsername(), loginUser.getPassword()
);
// 使用AuthencationManager来实现认证 ---- 1.
return authenticationManager.authenticate(authentication);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
*上面方法认证成功后调用
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 在下面讲到了,上面的操作最后会将JwtUser对象放入UsernamePasswordAuthenticationToken中
JwtUser user = (JwtUser) authResult.getPrincipal();
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
String token = JwtUtils.createJwtToken(user.getUsername(), roles, this.rememberMe);
response.setHeader(SecurityConstant.TOKEN_HEADER, token);
response.setContentType("text/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(ResultUtils.success(null).toString());
}
/**
*上面方法认证失败后调用
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, failed.getMessage());
}
}
--- 1.authenticationManager.authencate(authentication)实现
默认spring security会通过ProviderManager来实现
内部会继续使用provider.authenticate()方法来实现。provider会通过循环,找到适合的,如果没定义会有默认的实现。
首先会从缓存中查找是否有UserDetails对象存在,如果没有会有一个NullUserCache来实现,返回null
然后再通过retrieveUser()
方法,其默认实现的DaoAuthenticationProcider
来实现该方法
getUserDetailsService().loadUserByUsername会调用用户自己实现的类来获取到UserDetails,代码如下:其中JwtUser是自己设置的实现UserDetails的实现类
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
// 在config中已经设置了的密码加密
@Autowired
BCryptPasswordEncoder passwordEncoder;
/**
* 下面是自己模拟的数据,具体可以 通过这里传入的username从数据库中
* 查询用户然后再把用户的账号密码权限等信息存入JwtUser类中,再返回该类
*/
@Override
public UserDetails loadUserByUsername(String username) {
if (!"aa".equals(username)) {
throw new GlobalException("username" + username +"不存在");
}
String password = passwordEncoder.encode("aa");
JwtUser jwtUser = new JwtUser(1, "aa", password, new ArrayList<GrantedAuthority>() {{
add(new SimpleGrantedAuthority("ROLE_USER"));
add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}});
return jwtUser;
}
}