CustomerUserDetailsService的loadUserByUsername用来自定义用户信息和定义密码的编码
CustomerOncePerRequestFilter处理器是每次请求都会进去的;
当是WebSecurityConfig的permitAll范围,不管有没有setAuthentication(authentication)都有权限
当是WebSecurityConfig的authenticated,需要setAuthentication才有权限
一、pom依赖
1.POM依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
二、UserDetails
1、CustomerUserDetails实现security的UserDetails 接口
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class CustomerUserDetails implements UserDetails {
private String userId;
private String userName;
private String account;
private String password;
private Boolean available;
private Boolean adminFlag;
private Boolean validUrlFlag;
private Collection<? extends GrantedAuthority> authorities;
public CustomerUserDetails(String userId, String userName, String account, Collection<? extends GrantedAuthority> authorities) {
this.setUserId(userId);
this.setAccount(account);
this.setUserName(userName);
this.setAuthorities(authorities);
}
@Override
public String getUsername() {
return this.userName;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
2、CustomerUserDetailsService实现security的UserDetailsService接口
@Component
public class CustomerUserDetailsService implements UserDetailsService {
private final UserService userService;
public CustomerUserDetailsService(UserService userService) {
this.userService = userService;
}
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserListVO userListVO = userService.getByUserName(userName);
List<GrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
authorityList.add(new SimpleGrantedAuthority("角色2"));
return new CustomerUserDetails(userListVO.getId(), userListVO.getName(), userListVO.getName(), authorityList);
}
}
三、自定义处理器
1、CustomerOncePerRequestFilter继承OncePerRequestFilter类
这个处理器一定要有,核心代码SecurityContextHolder.getContext().setAuthentication(authentication)才有权限,不然会提示没有权限,也才能获取当前用户
@Component
public class CustomerOncePerRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("进入自定义jwt过滤器");
//1.解析jwt获取用户信息
// UserDetailVO userDetailVO = dbUser();
//2.用户信息转成CustomerUserDetails对象
CustomerUserDetails customerUserDetails = new CustomerUserDetails("用户id1", "用户名称1", "登录账号1", new ArrayList<>());
//3.customerUserDetails 转成UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customerUserDetails, "123451", customerUserDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//4将UsernamePasswordAuthenticationToken信息放入到上下文对象中, 这样后面的过滤器看到我们上下文对象中有authentication对象,就相当于我们认证过了
SecurityContextHolder.getContext().setAuthentication(authentication);
//5.放行
chain.doFilter(request, response);
}
}
2、认证失败处理器
/**
* 认证失败处理器
*/
@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
System.out.println("进入token认证失败处理器");
throw new RuntimeException("token无效,请重新登录");
}
}
3、权限不足处理器
/**
* 权限不足处理器
*/
@Component
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
System.out.println("进入权限不足处理器");
}
}
四、配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private SecurityAccessDeniedHandler restfulAccessDeniedHandler; //权限不足处理器
@Resource
private SecurityAuthenticationEntryPoint restAuthenticationEntryPoint; //认证失败处理器
@Resource
private CustomerOncePerRequestFilter customerOncePerRequestFilter; //自己的jwt过滤器
/**
* 自定义加密器Bean
* 强散列哈希加密实现,在Controller新增用户的时候用此bean生成密码,与及修改密码的时候用到此bean比较新旧密码是否匹配
*/
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
/**
* 定义AuthenticationManager
* 声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证
*/
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
auth.userDetailsService(userDetailsService());
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable() //由于使用的是JWT,我们这里不需要csrf
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //基于token,所以不需要session
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, //允许对于网站静态资源的无授权访问
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/swagger.**",
"/v3.**",
"**_notify_url"
).permitAll()
.antMatchers("/user/login", "/Register").permitAll() //对登录注册要允许匿名访问
.antMatchers(HttpMethod.OPTIONS).permitAll() //跨域请求会先进行一次options请求
// .antMatchers("/**").permitAll() //测试时全部运行访问
.anyRequest() //除上面外的所有请求全部需要鉴权认证
.authenticated()
.and()
.headers().cacheControl() //禁用缓存
;
//添加认证失败处理器和权限不足处理器
httpSecurity.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint) //认证失败处理器
.accessDeniedHandler(restfulAccessDeniedHandler); //权限不足处理器
//配置自己的JWT过滤器
httpSecurity.addFilterBefore(customerOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
五、获取当前用户SecurityUtils
public static Object getPrincipal() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
public class SecurityUtils {
private static final String USER_ID_KEY = "userId";
private static final String USERNAME_KEY = "username";
private static final String ADMIN_FLAG_KEY = "adminFlag";
private static final String VALID_URL_FLAG = "validUrlFlag";
/**
* 判断是否登录
*
* @return 是否登录
*/
public static boolean isLogin() {
Object principal = getPrincipal();
boolean isLogin = true;
if (principal instanceof String) {
// isLogin = !Constants.ANONYMOUS_USER.equals(principal.toString());
}
return isLogin;
}
/**
* 返修操作人ID
*
* @return 操作人ID
*/
public static String getOperatorId() {
Object principal = getPrincipal();
if (principal instanceof LinkedHashMap) {
return ((LinkedHashMap) principal).get(USER_ID_KEY).toString();
} else if (principal instanceof CustomerUserDetails) {
return ((CustomerUserDetails) principal).getUserId();
} else {
return "";
}
}
/**
* 返回登录人员是否admin
*
* @return 是否admin
*/
public static Boolean isAdmin() {
Object principal = getPrincipal();
if (principal instanceof LinkedHashMap) {
return (boolean) ((LinkedHashMap) principal).get(ADMIN_FLAG_KEY);
} else if (principal instanceof CustomerUserDetails) {
return ((CustomerUserDetails) principal).getAdminFlag();
} else {
return null;
}
}
/**
* 返回登录人员是否admin
*
* @param principal 授权信息map
* @return 是否admin
*/
public static Boolean isAdmin(Object principal) {
if (principal instanceof LinkedHashMap) {
return (boolean) ((LinkedHashMap) principal).get(ADMIN_FLAG_KEY);
} else if (principal instanceof CustomerUserDetails) {
return ((CustomerUserDetails) principal).getAdminFlag();
} else {
return null;
}
}
/**
* 返回是否验证url
*
* @param principal 授权信息map
* @return 是否验证url
*/
public static Boolean isValidUrl(Object principal) {
if (principal instanceof LinkedHashMap) {
return (boolean) ((LinkedHashMap) principal).get(VALID_URL_FLAG);
} else if (principal instanceof CustomerUserDetails) {
return ((CustomerUserDetails) principal).getValidUrlFlag();
} else {
return null;
}
}
/**
* 返回登录人员名称
*
* @return 登录人员名称
*/
public static String getOperatorName() {
Object principal = getPrincipal();
if (principal instanceof LinkedHashMap) {
return ((LinkedHashMap) principal).get(USERNAME_KEY).toString();
} else if (principal instanceof CustomerUserDetails) {
return ((CustomerUserDetails) principal).getUsername();
} else {
return "";
}
}
/**
* 获取用户详情
*
* @return 用户详情
*/
public static Object getPrincipal() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
六、登录业务,查询数据库生成token
@Override
public UserLoginVO login(UserLoginDTO userLoginDTO, HttpServletRequest request, HttpServletResponse response) {
String userName = userLoginDTO.getUserName();
String userPwd = userLoginDTO.getUserPassword();
//1.根据用户名查找用户
UserListVO dbUser = getByUserName(userName);
if (dbUser == null) {
throw new RuntimeException("登录用户不存在");
}
//2.判断密码是否一致
//SHA1加密
// userPwd = SHA1Util.encrytSHA1(userName, userPwd);
//密码不正确
if (!Objects.equals(userPwd, dbUser.getPassword())) {
throw new RuntimeException("用户密码错误");
}
//3.构建jwt负载信息对象,并生成jwt
UserJWTPayLoadBO userJWTPayLoadBO = new UserJWTPayLoadBO();
userJWTPayLoadBO.setUserId(dbUser.getId());
userJWTPayLoadBO.setUserName(dbUser.getName());
String ip = JwtUtils.getClientHost(request);
userJWTPayLoadBO.setIpAddress(ip);
Map<String, Object> userJWTPayLoadMap = JSONObject.parseObject(JSONObject.toJSONString(userJWTPayLoadBO), new TypeReference<>() {
});
String jwt = JwtUtils.generateJWT(userJWTPayLoadMap);
//4.jwt信息存入token
Cookie cookie = new Cookie(CommonConstants.USER_TOKEN, jwt);
cookie.setPath("/");
cookie.setMaxAge(60 * 60 * 24 * 7);
response.addCookie(cookie);
//5.返回用户信息和jwt
UserLoginVO userLoginVO = new UserLoginVO();
userLoginVO.setToken(jwt);
userLoginVO.setUserInfo(dbUser);
return userLoginVO;
}