JWT:json web token,是目前最流行的一个跨域认证解决方案:客户端发起用户登录请求,服务器端接收并认证成功后,生成一个 JSON 对象,然后将其返回给客户端。
1.用户携带username和password请登录
2.服务器验证登录验证,如果验证成功,根据用户的信息和服务器的规则生成JWT Token
3.服务器将该token返回
4.用户得到token,存在localStorage、cookie或其它数据存储形式中。
5.以后用户请求服务器时,在请求的header中加入 Authorization:xxxx(token) 。服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和业务逻辑反回响应结果。
1.用户使用username和password登录;
2 用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
3 AuthenticationManager 身份管理器负责验证这个Authentication
4 认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
5 SecurityContextHolder安全上下文容器将第4步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
/**
* @Description 安全配置
* @Date
*
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
/**
* 实例化JwtAuthenticationProvider
*
* @return
*/
@Bean
JwtAuthenticationProvider authenticationProvider() {
JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(myUserDetailService);
jwtAuthenticationProvider.setPasswordEncoder(passwordEncoder);
return jwtAuthenticationProvider;
}
/**
* 将provider添加到authenticationProviders集合中
* 在ProviderManager.authenticate(Authentication authentication)方法中会调用相关的provider
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
/**
* 配置spring secrurity
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
http.cors().and().csrf().disable()
.authorizeRequests()
// 跨域预检请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 查看SQL监控(druid)
.antMatchers("/druid/**").permitAll()
// 首页和登录页面
.antMatchers("/").permitAll()
.antMatchers("/unAuth/**").permitAll()
.antMatchers("/accessToken/**").permitAll()
.antMatchers("/emp/**").permitAll()
.antMatchers("/employee/**").permitAll()
.antMatchers("/customer/**").permitAll()
// 服务监控
.antMatchers("/actuator/**").permitAll()
// swagger
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/configuration/**").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/webjars/**").permitAll()
//供内部RPC
.antMatchers("/verificationCodeEx/**").permitAll()
.antMatchers("/authClientEx/**").permitAll()
//IAM相关
.antMatchers("/iam/**").permitAll()
// 其他所有请求需要身份认证
.anyRequest().authenticated();
// 退出登录处理器
http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
// token验证过滤器
http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
/**
* 获取AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
/**
* 登录认证过滤器
* 继承 BasicAuthenticationFilter,在访问任何URL的时候会被此过滤器拦截
*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
/**
* 构造器
*
* @param authenticationManager
*/
@Autowired
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 过滤逻辑
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 从http请求中获取token, 并在上下文中记录认证信息
// SecurityUtils.checkAuthentication(request);
chain.doFilter(request, response);
}
}
/**
* PC端,员工登录,手机号+短信验证码
* 无需认证,直接对外开放
*/
@ApiOperation(value = "PC端员工通过 手机号+短信验证码 登录")
@PostMapping(value = "/unAuth/loginPC2")
public HttpResult loginPCByMobileSMS(@RequestBody LoginBean loginBean, HttpServletRequest request) throws Exception {
JwtAuthenticatioToken token = this.employeeLoginMainService.loginByMobileSMS(loginBean, request);
//擦除密码
token.eraseCredentials();
return HttpResult.successWithData(token);
}
/**
* 员工通过 手机号+短信验证码 登录并生成token
*
* @param loginBean
* @param request
* @return
* @throws Exception
*/
public JwtAuthenticatioToken loginByMobileSMS(LoginBean loginBean, HttpServletRequest request) throws Exception {
JwtAuthenticatioToken token = this.employeeLoginBaseService.handleLogin(loginBean, request);
return token;
}
/**
* 生成token的核心逻辑
* @param loginBean
* @param request
* @return
*/
@Override
public JwtAuthenticatioToken handleLogin(LoginBean loginBean, HttpServletRequest request) {
// 系统登录认证,生产token
Map map = SecurityUtils.login(request, loginBean, this.authenticationManager);
String token = AuthConstants.TOKEN_PREFIX + RandomStringUtils.randomAlphanumeric(10) + String.valueOf(idGenerator.nextId());
String authorities = (String) map.get("authorities");
Map<String, Object> claims = (Map<String, Object>) map.get("claims");
//保存新token
this.redisService.saveToken(token,
JSON.toJSONString(new TokenValueInRedis(authorities, claims)),
LoginRelatedHelper.getLoginDeviceType(loginBean.getUserLoginType()));
//员工登陆表中记录客户登陆成功后产生的token
return new JwtAuthenticatioToken(loginBean.getName(), null, token);
}
/**
* Security相关操作
*/
public class SecurityUtils {
/**
* 构造用于生产令牌的Claims
*/
public static Map<String, Object> buildClaimsMap(LoginBean loginBean) {
Map<java.lang.String, java.lang.Object> claims = new HashMap<>(10);
claims.put(JwtTokenUtils.USERID, loginBean.getId());
claims.put(JwtTokenUtils.USERNAME, loginBean.getName());
claims.put(JwtTokenUtils.USERLOGINTYPE, loginBean.getUserLoginType());
claims.put(JwtTokenUtils.COMPANYID, loginBean.getCompanyId());
claims.put(JwtTokenUtils.COMPANYNAME, loginBean.getCompanyName());
claims.put(JwtTokenUtils.DEPTID, loginBean.getDeptId());
claims.put(JwtTokenUtils.DEPTNAME, loginBean.getDeptName());
claims.put(JwtTokenUtils.CREATED, new Date());
claims.put(JwtTokenUtils.AUTHORITIES, "");
claims.put(JwtTokenUtils.CLIENTID, loginBean.getClientId() == null ? "" : loginBean.getClientId());
claims.put(JwtTokenUtils.INVITATIONCODE, loginBean.getInvitationCode()); //邀请码
return claims;
}
/**
* 生成令牌相关的 Claims
*/
public static Map<String, Object> generateClaims(Authentication authentication, LoginBean loginBean) {
Long userid = getUserID(authentication);
String username = getUsername(authentication);
loginBean.setId(userid);
loginBean.setName(username);
Map<String, Object> claims = buildClaimsMap(loginBean);
// return JwtTokenUtils.generateToken(claims);
return claims;
}
/**
* 系统登录认证
*
*/
public static Map<String, Object> login(HttpServletRequest request, LoginBean loginBean, AuthenticationManager authenticationManager) {
JwtAuthenticatioToken token = new JwtAuthenticatioToken(loginBean.getAccount(), loginBean.getPassword());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 执行登录认证过程
Authentication authentication = authenticationManager.authenticate(token);
// 认证成功存储认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
Map<String, Object> claims = generateClaims(authentication, loginBean);
HashMap<String, Object> map = new HashMap<>();
map.put("claims", claims);
String authorites = authentication.getAuthorities().stream().map(authority -> ((GrantedAuthority) authority).getAuthority()).collect(Collectors.joining(";"));
map.put("authorities", authorites);
return map;
}
/**
* 获取用户id
* @return
*/
private static Long getUserID(Authentication authentication) {
Long userid = null;
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof JwtUserDetails) {
userid = ((JwtUserDetails) principal).getUserid();
}
}
return userid;
}
/**
* 获取用户名
* @return
*/
private static String getUsername(Authentication authentication) {
String username = null;
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof JwtUserDetails) {
username = ((JwtUserDetails) principal).getUsername();
}
}
return username;
}
}
/**
* 自定义用户模型,用于JWT
*/
public class JwtUserDetails implements UserDetails {
private Long userid;
private String username;
private String password;
private String salt;
private Collection<? extends GrantedAuthority> authorities;
/**
* 构造器
*
* @param userid
* @param username
* @param password
* @param salt
* @param authorities
*/
public JwtUserDetails(Long userid, String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {
this.userid = userid;
this.username = username;
this.password = password;
this.salt = salt;
this.authorities = authorities;
}
/**
* 构造器
*
* @param userid
* @param username
* @param password
* @param authorities
*/
public JwtUserDetails(Long userid, String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(userid, username, password, DEFAULT_SALT, authorities);
}
/**
* 获取用户id
*
* @return
*/
public Long getUserid() {
return userid;
}
/**
* 获取用户姓名
*
* @return
*/
@Override
public String getUsername() {
return username;
}
/**
* 获取密码
*
* @return
*/
@JsonIgnore
@Override
public String getPassword() {
return password;
}
/**
* 获取盐
*
* @return
*/
public String getSalt() {
return salt;
}
/**
* 获取权限标识集合
*
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
/**
* 账户是否过期
*
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账户是否锁定
*
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 认证是否过期
*
* @return
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否有效
*
* @return
*/
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
/**
* 自定义令牌对象
*/
public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = 1L;
private String token;
/**
* 构造器
*
* @param principal
* @param credentials
*/
public JwtAuthenticatioToken(Object principal, Object credentials) {
super(principal, credentials);
}
/**
* 构造器
*
* @param principal
* @param credentials
* @param token
*/
public JwtAuthenticatioToken(Object principal, Object credentials, String token) {
super(principal, credentials);
this.token = token;
}
/**
* 构造器
*
* @param principal
* @param credentials
* @param authorities
* @param token
*/
public JwtAuthenticatioToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String token) {
super(principal, credentials, authorities);
this.token = token;
}
/**
* 获取token
*
* @return
*/
public String getToken() {
return token;
}
/**
* 设置token
*
* @param token
*/
public void setToken(String token) {
this.token = token;
}
/**
* 获取序列化id
*
* @return
*/
public static long getSerialversionuid() {
return serialVersionUID;
}
}
/**
* 自定义 provider
* additionalAuthenticationChecks 方法中进行密码正确性校验
*/
@Data
@Slf4j
public class JwtAuthenticationProvider extends DaoAuthenticationProvider {
//是否跳过基于SpringSecurity的密码校验
public static ThreadLocal<Boolean> isNeedCheckPassword = ThreadLocal.withInitial(() -> true);
private BCryptPasswordEncoder passwordEncoder;
/**
* 构造器
*
* @param userDetailService
*/
public JwtAuthenticationProvider(UserDetailsService userDetailService) {
setUserDetailsService(userDetailService);
}
/**
* 自定义的密码校验
*
* @param userDetails
* @param authentication
* @throws AuthenticationException
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
//先判断是否需要用户名和密码校验
if (!isNeedCheckPassword.get()) {
return;
}
if (authentication.getCredentials() == null) {
throw new BaseException(HttpStatus.BadRequest.getStatus(), "请输入密码!");
}
//用户录入的密码,明文
String presentedPassword = authentication.getCredentials().toString();
//盐值
String salt = ((JwtUserDetails) userDetails).getSalt();
// 覆写密码验证逻辑
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BaseException(HttpStatus.BadRequest.getStatus(), "用户名密码错误");
}
}
}
/**
* 权限集合的封装类
*/
public class GrantedAuthorityImpl implements GrantedAuthority {
private static final long serialVersionUID = 1L;
private String authority;
/**
* 构造器
*
* @param authority
*/
public GrantedAuthorityImpl(String authority) {
this.authority = authority;
}
/**
* 设置权限
*
* @param authority
*/
public void setAuthority(String authority) {
this.authority = authority;
}
/**
* 获取权限
*
* @return
*/
@Override
public String getAuthority() {
return this.authority;
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.authentication.dao;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public DaoAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
spring security验证过程:
1.userName、passWord生成 JwtAuthenticatioToken(JwtAuthenticatioToken token = new JwtAuthenticatioToken(loginBean.getAccount(), loginBean.getPassword());)
2.AuthenticationManager进行验证(Authentication authentication = authenticationManager.authenticate(token);)
配置文件中myUserDetailService实例化JwtAuthenticationProvider(继承DaoAuthenticationProvider–>AuthenticationManager的实现类)
DaoAuthenticationProvider 调用 本身retrieveUser方法,此方法再调用myUserDetailService 的loadUserByUsername方法查询用户信息、用户权限,DaoAuthenticationProvider 调用本身additionalAuthenticationChecks方法进行密码校验,验证通过返回填充满的Authentication 对象,不通过报错;
3.将Authentication 对象设置进SecurityContextHolder中(SecurityContextHolder.getContext().setAuthentication(authentication);)
SecurityContextHolder默认使用ThreadLocal 策略来存储认证信息。看到ThreadLocal 也就意味着,这是一种与线程绑定的策略。
spring security 基础