1.pom加依赖
<!--springsecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.securityconfig
咱这个认证服务就是颁发token的,所有咱们放行所有springsecurity的认证。然后自己来通过调用auth来认证。如果没有认证通过,则通过handler来返回错误信息
然后就是给auth设置好userdetailservie和密码校验的那个BCryptPasswordEncoder
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
authorizeRequests().
antMatchers("/**").permitAll().
and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).
and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/**
* 如果你想使用AuthenticationManager这个玩意进行显示的校验用户名密码那么就需要自己手动注入一下,如果不显示的使用校验,
* springsecurity会自己注入并完成使用,因为咱们在生成token的接口里面用到了这个校验了,所以咱们注入一下
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 进行密码加密校验。通常你配置了这个玩意,你数据库的密码是加密的,你传入过来的密码会通过这个玩意加密再匹配
*
* @return
*/
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 它的主要作用是配置全局的身份验证管理器 AuthenticationManagerBuilder
* 就是配置UserDetailsService和BCryptPasswordEncoder
*
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
}
3.service
@Service
public class AccountServiceImpl implements AccountService {
Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
@Autowired
private ConsumerClient consumerClient;
@Autowired
private AccountClient accountClient;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
public ResponseResult<String> register(AccountDTO accountDTO) {
//1.判断必要参数
if (StrUtil.isBlank(accountDTO.getEmail()) || StrUtil.isBlank(accountDTO.getVeriCode())) {
throw new RuntimeException("参数异常");
}
//2.获取是否注册过
Integer num = accountClient.getAccountByEmail(accountDTO.getEmail());
logger.info("{} if exist num==>{}", accountDTO.getEmail(), num);
//3.进行注册or登录-
//3.1 -登录就是返回一个token
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
accountDTO.getEmail(),
accountDTO.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = jwtTokenProvider.createToken(accountDTO.getEmail(), new ArrayList<>());
// 3.2 todo 没有注册则进行注册,注册后返回token,需要一个默认密码
return ResponseResult.okResult(token);
}
}
authenticationManager.authenticate() 传入我们的用户名密码,然后springsecurity就会帮我们往下进行验证
主要流程咱们画个图
4.CustomUserDetailsService
上面调用的方法最终就会找到这个loadUserByUsername()方法
@Service
public class CustomUserDetailsService implements UserDetailsService {
Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
@Autowired
private AccountClient accountClient;
/**
* 这部分是核心,会在authenticate调用的时候执行该方法
*
* @param username the username identifying the user whose data is required.
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AccountVO account = accountClient.findByUsername(username);
logger.info("query account is {}", JSON.toJSONString(account));
// 这里就是进行查询用户并判断是否存在
if (account == null) {
throw new UsernameNotFoundException("User not found");
}
return UserPrincipal.create(account);
}
}
然后我们再看这个方法的返回。他要求你必须是一个UserDetails类,我们想要灵活配置就自己实现UserDetails
5.UserDetails
这个里面有todo,就是可以在创建这个对象的时候给设置上值。这样就可以根据我们数据库查询的accountvo来设置,这样springsecurity就根据后面的方法来获取值,判断你的账号是否可用之类的
/**
* 这个类也是在authenticate校验时进行各种判断,什么过期,是否可用的账号之类的
*/
public class UserPrincipal implements UserDetails, Serializable {
private Long id;
private String email;
private String password;
private Set<GrantedAuthority> authorities;
public UserPrincipal() {
}
public UserPrincipal(Long id, String email, String password, Set<GrantedAuthority> authorities) {
this.id = id;
this.email = email;
this.password = password;
this.authorities = authorities;
}
public static UserPrincipal create(AccountVO accountVO) {
Set<GrantedAuthority> authorities = new HashSet<>();
// user.getRoles().stream().map(role ->
// new SimpleGrantedAuthority(role.getName().name())
// ).collect(Collectors.toList());
return new UserPrincipal(
accountVO.getId(),
accountVO.getEmail(),
accountVO.getPassword(),
authorities
);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.email;
}
public void setUsername(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isAccountNonExpired() {
// todo
return true;
}
@Override
public boolean isAccountNonLocked() {
// todo
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// todo
return true;
}
@Override
public boolean isEnabled() {
// todo
return true;
}
// ...其他代码,包括getters和实现UserDetails接口的方法...
}
6.JwtTokenProvider
@Component
public class JwtTokenProvider {
private String secretKey = "secret";
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + 3600000); //设置Token有效期为1小时
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
}
7.JwtAuthenticationEntryPoint
/**
* 这个是一个没有校验通过的错误返回器
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}