目录
1.创建User实体类实现UserDetails并重写其方法
2.创建UserDetailsServiceImpl实现UserDetailsService
1.创建User实体类实现UserDetails并重写其方法
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("sys_user")
public class User implements UserDetails {
@TableId
private Long id;
private String username;
private String password;
private String nickname;
private Integer sex;
private Boolean status;
private Integer isAdmin;
private String avatar;
private String address;
private String openId;
private String phone;
private String email;
@TableField(exist = false)
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
if (role.getCode().startsWith("ROLE_")) {
authorities.add(new SimpleGrantedAuthority(role.getCode()));
}
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return status;
}
}
2.创建UserDetailsServiceImpl实现UserDetailsService
@Service
@Transactional
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (ObjectUtils.isEmpty(user)){
throw new UsernameNotFoundException("用户名或密码不存在");
}
return user;
}
}
3.security配置中暴露一个登录接口并处理登录逻辑
- 暴露/user/login接口
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//- 放行暴露的登录接口
.antMatchers("/user/login").anonymous()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
}
- 处理登录逻辑
@Service
@Transactional
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private JwtConfig jwtConfig;
@Override
public RespBean login(LoginUser loginUser) {
log.info("1.开始登录");
UserDetails userDetails = userDetailsService.loadUserByUsername(loginUser.getUsername());
log.info("2.判断密码是否正确");
if (!passwordEncoder.matches(loginUser.getPassword(),userDetails.getPassword())){
return RespBean.fail("账号或密码错误,请重新输入");
}
log.info("3.判断账号是否禁用");
if (!userDetails.isEnabled()){
return RespBean.fail("账号已禁用,请联系管理员");
}
log.info("4.登录成功,将用户信息保存到SecurityContextHolder");
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()));
log.info("5.生成jwt并将tokenHead和token返回给前端");
String token = jwtUtils.generatorToken(userDetails);
Map<String ,Object> map = new HashMap<>(2);
map.put("tokenHead",jwtConfig.getTokenHead());
map.put("token",token);
return RespBean.success("登录成功",map);
}
}
4.配置jwt过滤器
@Component
@Slf4j
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtConfig jwtConfig;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws ServletException, IOException {
log.info("1.从req中获取header");
String header = req.getHeader(jwtConfig.getTokenHeader());
if (StringUtils.hasText(header) && header.startsWith(jwtConfig.getTokenHead())) {
log.info("2.获取token");
String token = header.substring(jwtConfig.getTokenHead().length());
log.info("3.判断token是否过期");
if (!jwtUtils.isExpired(token)) {
log.info("4.从token中获取用户名并从SecurityContextHolder中获取authentication");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//- 如果是非法token则获取不到username,会抛出异常并被OncePerRequestFilter的捕获,从而由于没有登录记录,返回401
String username = jwtUtils.getUsernameFromToken(token);
if (StringUtils.hasText(username) && ObjectUtils.isEmpty(authentication)) {
log.info("token中获取到的用户名不为空,但是没有登录记录,则先登录处理");
log.info("4.1 登录");
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
log.info("4.2 将用户信息保存到SecurityContextHolder中");
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()));
log.info("4.3 登录成功--->{}", SecurityContextHolder.getContext().getAuthentication());
}
}
}
//- 放行
chain.doFilter(req, resp);
}
}
5.配置未登录就访问资源和权限不足的异常处理类
- 未登录就访问资源
@Configuration
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter pw = resp.getWriter();
pw.write(new ObjectMapper().writeValueAsString(RespBean.fail("请先登录")));
pw.flush();
pw.close();
}
}
- 权限不足
@Configuration
public class MyAccessDenied implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse resp, AccessDeniedException accessDeniedException) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter pw = resp.getWriter();
pw.write(new ObjectMapper().writeValueAsString(RespBean.fail("权限不足,请联系管理员")));
pw.flush();
pw.close();
}
}
6.在security配置类中添加jwt过滤器和异常处理类
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtFilter jwtFilter;
@Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
@Autowired
private MyAccessDenied myAccessDenied;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//- 放行暴露的登录接口
.antMatchers("/user/login").anonymous()
.anyRequest().authenticated()
.and()
.csrf().disable();
//- 各种异常处理
http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDenied);
//- 添加过滤器
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
附录:
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/sport?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jwt:
tokenHeader: Authorization
secret: asfasfsd
expiration: 1800
tokenHead: 'Barber '
mybatis-plus:
mapper-locations: classpath:mappers/*.xml
JwtConfig
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
private String tokenHeader;
private String secret;
private Long expiration;
private String tokenHead;
}
JwtUtils
@Component
public class JwtUtils {
@Autowired
private JwtConfig jwtConfig;
/**
* 根据用户名和创建时间生成token
*
* @param userDetails 用户信息
* @return
*/
public String generatorToken(UserDetails userDetails) {
Map<String, Object> map = new HashMap<>(2);
map.put("username", userDetails.getUsername());
map.put("created", new Date());
return generatorJwt(map);
}
/**
* 根据荷载信息生成jwt
*
* @param map
* @return
*/
public String generatorJwt(Map<String, Object> map) {
return Jwts.builder()
.setClaims(map)
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000))
.compact();
}
/**
* 解析token并得到实体,若token不合法则返回null
*
* @param token
* @return
*/
public Claims getTokenBody(String token) {
try {
return Jwts.parser()
.setSigningKey(jwtConfig.getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 从token中得到用户名
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
return (String) getTokenBody(token).get("username");
}
public boolean isExpired(String token){
return getTokenBody(token).getExpiration().before(new Date());
}
public String refreshTokenExpiration(String token){
Claims claims = getTokenBody(token);
claims.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000));
return generatorJwt(claims);
}
}
RespBean
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class RespBean {
public boolean flag;
private String msg;
private Object data;
public static RespBean success(String msg,Object o){
return new RespBean(true,msg,o);
}
public static RespBean fail(String msg){
return new RespBean(false,msg,null);
}
}
LoginUser
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class LoginUser {
private String username;
private String password;
}
项目结构