pom:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.0</version><relativePath/><!-- lookup parent from repository --></parent><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.2.2.RELEASE</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.8.1</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
结构图:
- WebSecurityConfig:此类是入口,统筹以下这些类。
- JwtAuthenticationProvider : 用于登录用户名密码是否正确。
- JwtAuthenticationTokenFilter:拦截请求过来的token(类似于olp的公有域),判断token是否过期和正确。
- PermissionCheckServiceImpl:判断请求进来的接口(除登录外)且controller方法上标识@PreAuthorize(“@per.havePermission(‘李静’)”)注解的判断是否有请求当前接口的权限。
- UserDetailsServiceImpl 和 UserDetailsBo 这两个类用于把需要验证接口的权限字符写入集合。
- WAccessDeniedHandler:用于权限失败的返回信息。
- AuthController:登录或者其他接口的controller,生成token也是在登录方法里(具体实现LoginServiceImpl)。
- AuthConstant:定义密钥(这个名字随便起)。
具体代码:
WebSecurityConfig:
package org.example.bootConfig;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig{@Autowiredprivate WAccessDeniedHandler wAccessDeniedHandler;/*** 认证管理* @param configuration* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {return configuration.getAuthenticationManager();}/*** 认证过滤器* @return*/@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}/*** 密码加密* @return*/@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}/*** 权限校验* @return*/@Bean("per")public PermissionCheckServiceImpl permissionCheckServiceImpl(){return new PermissionCheckServiceImpl();}/*** 配置安全过滤器链* @param httpSecurity https://blog.csdn.net/yuhaowu0422/article/details/142343885* @return* @throws Exception*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.csrf(AbstractHttpConfigurer::disable).sessionManagement(sessionManager-> sessionManager.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeRequests().antMatchers("/auth/login").permitAll().anyRequest().authenticated();httpSecurity.formLogin(Customizer.withDefaults())//.loginPage("/login.html") // 登录页(默认Security会提供一个登录页)//.usernameParameter("username")//.passwordParameter("password")//.failureForwardUrl("/login?error")//.and()返回了HttpSecurity本身.logout()//.logoutUrl("/logout")//.logoutSuccessUrl("/index").permitAll().and().httpBasic(Customizer.withDefaults())//禁用缓存.headers(header->header.cacheControl(HeadersConfigurer.CacheControlConfig::disable)).addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)//鉴权失败类.exceptionHandling(exce->exce.accessDeniedHandler(wAccessDeniedHandler));return httpSecurity.build();}
}
JwtAuthenticationProvider:
package org.example.bootConfig;import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;/*** 用户身份验证,用户登录用户名和密码校验是否正确**/
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
// @Autowired
// private PasswordEncoder passwordEncoder;@Autowiredprivate UserDetailsService userDetailsService;@Overridepublic Authentication authenticate(Authentication authentication) {String username = String.valueOf(authentication.getPrincipal());String password = String.valueOf(authentication.getCredentials());UserDetails userDetails = userDetailsService.loadUserByUsername(username);if(userDetails != null && StringUtils.isNotBlank(userDetails.getPassword())&& userDetails.getPassword().equals(password)){return new UsernamePasswordAuthenticationToken(username,password,authentication.getAuthorities());}throw new RuntimeException(RespCodeEnum.NAME_OR_PASSWORD_ERROR.getCode());}@Overridepublic boolean supports(Class<?> authentication) {return UsernamePasswordAuthenticationToken.class.equals(authentication);}
}
JwtAuthenticationTokenFilter:
package org.example.bootConfig;import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.example.model.CaiwuUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;import static org.example.util.AuthConstant.JWT_KEY;/**** 身份验证拦截器*/
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//从头中获取token(jwt)String authorization = request.getHeader("Authorization");//判断tokenif(StringUtils.isBlank(authorization)){//登录到了这,会调AuthController中实际请求的方法,如果是登录直接不校验,然后在生成tokenfilterChain.doFilter(request, response);return ;}//校验token格式if(!authorization.startsWith("Bearer ")){log.error(RespCodeEnum.TOKEN_ERROR.getDescription());response(response, RespCodeEnum.TOKEN_ERROR);return ;}//获取jwt数据String token = authorization.split(" ")[1];if(!JWTUtil.verify(token, JWT_KEY.getBytes(StandardCharsets.UTF_8))){log.error(RespCodeEnum.TOKEN_ERROR.getDescription());response(response, RespCodeEnum.TOKEN_ERROR);}//获取用户名和过期时间JWT jwt = JWTUtil.parseToken(token);//caiwuUser这个返回的是整条登录用户信息String caiwuUser = (String) jwt.getPayload("caiwuUser");CaiwuUser caiwuUserObj = JSON.parseObject(caiwuUser, CaiwuUser.class);//获取jwt中的过期时间long exp = Long.valueOf(String.valueOf(jwt.getPayload("exp")));//判断是否已经过期if(System.currentTimeMillis() / 1000 > exp){log.error(RespCodeEnum.TOKEN_EXP.getDescription());response(response, RespCodeEnum.TOKEN_EXP);return;}//获取用户信息UserDetailsBo userDetails = (UserDetailsBo)userDetailsService.loadUserByUsername(caiwuUserObj.getUsername());UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),userDetails.getPassword(), userDetails.getAuthorities());authenticationToken.setDetails(userDetails.getCaiwuUser());//将认证过了凭证保存到security的上下文中以便于在程序中使用SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request,response);}private void response(@NotNull HttpServletResponse response, @NotNull RespCodeEnum error) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定义状态码response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");// todo response.getWriter().write(JSONUtil.toJsonStr(ResponseDto.fail(error)));response.getWriter().write("未认证");}
}
PermissionCheckServiceImpl:
package org.example.bootConfig;import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;public class PermissionCheckServiceImpl {public PermissionCheckServiceImpl(){}public boolean havePermission(String... permissions) {if(permissions == null){return true;}Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if(authentication != null){//获取到的getAuthentication()值其实就是角色信息名,这里把 '李静' 当成角色了//呼应AuthController 中 @PreAuthorize("@per.havePermission('user','admin')") 的 user和admin角色Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();List<String> authList = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());for(int i = 0;i < permissions.length;i++){if(authList.contains(permissions[i])){return true;}}}return false;}
}
UserDetailsBo:
package org.example.bootConfig;import org.example.model.CaiwuUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;@Component
public class UserDetailsBo implements UserDetails {private CaiwuUser caiwuUser;/*** SimpleGrantedAuthority 放该用户名下所有的角色。。。。咱这里把用户名当成角色就好。* @return*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {// return caiwuUser.getPermissionName().stream()
// .map(SimpleGrantedAuthority::new).collect(Collectors.toList());List<String> list = new ArrayList<>();list.add(caiwuUser.getUsername());return list.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}@Overridepublic String getPassword() {return caiwuUser.getPass();}@Overridepublic String getUsername() {return caiwuUser.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}public UserDetailsBo(){}public UserDetailsBo(CaiwuUser caiwuUser){this.caiwuUser = caiwuUser;}public CaiwuUser getCaiwuUser() {return caiwuUser;}public void setCaiwuUser(CaiwuUser caiwuUser) {this.caiwuUser = caiwuUser;}
}
UserDetailsServiceImpl:
package org.example.bootConfig;import lombok.RequiredArgsConstructor;
import org.example.mapper.LoginMapper;
import org.example.model.CaiwuUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate LoginMapper loginMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//UserDto user = userService.selectUserByLoginName(username);List<CaiwuUser> list = loginMapper.queryZhuce(username,null);CaiwuUser caiwuUser = list.get(0);return new UserDetailsBo(caiwuUser);}
}
LoginMapper:
package org.example.mapper;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.example.model.CaiwuUser;import java.util.List;@Mapper
public interface LoginMapper {@Param("password") String password);List<CaiwuUser> queryZhuce(@Param("username")String username,@Param("pass")String pass);
}
LoginMapper.xml:
<mapper namespace="org.example.mapper.LoginMapper"><select id="queryZhuce" parameterType="java.lang.String" resultType="org.example.model.CaiwuUser">select * from caiwuuser where username = #{username}</select>
</mapper>
WAccessDeniedHandler:
package org.example.bootConfig;import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/**** 权限认证失败处理*/
@Component
public class WAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json");response.getWriter().print("认证失败");response.getWriter().flush();}
}
RespCodeEnum:
package org.example.bootConfig;public enum RespCodeEnum {NAME_OR_PASSWORD_ERROR("101","用户名或者密码错误"),TOKEN_ERROR("102","token错误"),TOKEN_EXP("103","token过期");private String description;//这个必须定义,且成员变量的类型及个数必须对应于上边枚举的定义//枚举标识码(英文描述)private String code;RespCodeEnum(String code, String description) {this.code = code;this.description = description;}public String getCode() {return code;}public String getDescription() {return description;}public static String getDescriptionByCode(String code) {for (RespCodeEnum value : RespCodeEnum.values()) {if (value.getCode().equals(code)) {return value.getDescription();}}return null;}
}
AuthController:
package org.example.controller;import lombok.RequiredArgsConstructor;
import org.example.model.CaiwuUser;
import org.example.model.UserInfoVo;
import org.example.service.ILoginService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 认证控制器**/@RestController
@RequestMapping("auth")
@RequiredArgsConstructor
public class AuthController {private final ILoginService loginService;/*** 登录* @param caiwuUser 请求参数* @return 返回token*/@GetMapping("login")public String login(@Validated @RequestBody CaiwuUser caiwuUser) {return loginService.loginAccPwd(caiwuUser);}//@PreAuthorize("@per.havePermission('user','admin')")@PreAuthorize("@per.havePermission('李静')")@GetMapping("test")public UserInfoVo test() {return null;}
}
ILoginService:
package org.example.service;import org.example.model.CaiwuUser;public interface ILoginService {String loginAccPwd(CaiwuUser req);
}
LoginServiceImpl:
package org.example.service;import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.RequiredArgsConstructor;
import org.example.model.CaiwuUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service;import java.nio.charset.StandardCharsets;
import java.util.Date;import static org.example.util.AuthConstant.JWT_KEY;@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements ILoginService {private final AuthenticationManager authenticationManager;/*** 定义密钥*///private static final byte[] key = "book".getBytes(StandardCharsets.UTF_8);@Overridepublic String loginAccPwd(CaiwuUser caiwuUser) {//登录验证UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(caiwuUser.getUsername(), caiwuUser.getPass());//这一步实际上会去调UserDetailsServiceImpl中方法去查库查到该账号对应的账号信息authenticationManager.authenticate(authentication);/*** JWT包含了三部分:* Header 头部* Payload 负载 这里可以把委托中的操作员,角色,机构信息放进去加密* Signature 签名/签证*///生成jwt tokenString token = JWT.create().setPayload("caiwuUser", JSON.toJSONString(caiwuUser)).setKey(JWT_KEY.getBytes(StandardCharsets.UTF_8))//过期时间2分钟.setExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 10)).sign();return token;}/** https://blog.csdn.net/XiaYeTende/article/details/140275139** DateTime signTime = DateUtil.date();* String token = JWT.create()* .setIssuedAt(signTime)* .setExpiresAt(DateTime.of(LocalDateTimeUtil.of(signTime).plusMinutes(1)* .atZone(ZoneId.systemDefault())* .toInstant()* .toEpochMilli()))* .setPayload("userId", userInfo.getUserId())* .setPayload("bookStoreId", bookStoreId)* .setKey(key)* .setSigner("HS256", key)* .sign();*/
}
AuthConstant:
/*** 定义密钥*/
public class AuthConstant {public static final String JWT_KEY = "book";
}
CaiwuUser:
package org.example.model;import lombok.Data;import java.io.Serializable;@Data
public class CaiwuUser implements Serializable {private int id;private String username;private String pass;private int age;//如果ip是作为返给前端那么暂时作为天气描述字段private String ip;private String work;private String updatetime;private String createtime;private String phonenumber;private String ipLocation;
}
UserInfoVo(用于测试test方法的能不能进去,所以是空的):
public class UserInfoVo {
}