mave
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.7.0version>
dependency>
在springboot的 application.yaml 配置文件中
# JWT
jwt:
# JWT存储的请求头
tokenHeader: Authorization
# JWT 解密加密使用的密钥
secret: yeb-secret
# JWT的超期限时间(30*60*24)
expiration: 604800
# JWT 负载中拿到开头
tokenHead: Bearer
编写配置工具类:JwtTokenUtil
/**
* JwtTokenUtil工具类
*/
@Component
public class JwtTokenUtil {
// 用户名的key
private static final String CLAIM_KEY_USERNAME = "sub";
// JWT 的创建时间
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")//从yaml文件中取值:yeb-secret
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根据用户信息生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 根据荷载生成JWT Token
*/
public String generateToken(Map<String, Object> claims) {
System.out.println("secret" + secret);
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate()) // 失效时间
.signWith(SignatureAlgorithm.HS512, secret) // 签名
.compact();
}
/*
* 生成失效时间
*/
public Date generateExpirationDate() {
// 失效时间是就当前系统时间加 有效时间
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取登录的用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getUserClaimFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
username = null;
}
return username;
}
/**
* 从token中获取荷载
*/
private Claims getUserClaimFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
e.printStackTrace();
}
return claims;
}
/**
* 验证token是否有效 (token有效 + 用户一致)
*/
public Boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);// 源于token
// 然后在token有效的前提下对比用户名是否一致
return !isTokenExpired(token) && username.equals(userDetails.getUsername());
}
/**
* 判断token是否以及失效
*/
public boolean isTokenExpired(String token) {
Date expireDate = getExpiredDateFromToken(token);
return expireDate.before(new Date());
}
/**
* 获取token过期时间
*/
public Date getExpiredDateFromToken(String token) {
Claims claims = getUserClaimFromToken(token);
return claims.getExpiration();
}
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getUserClaimFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
private long code;
private String message;
private Object result;
/**
* 成功返回 (提示)
* @param message
* @return
*/
public static RespBean success(String message) {
return new RespBean(200, message, null);
}
/**
* 成功返回 (结果)
* @param message
* @param result
* @return
*/
public static RespBean success(String message, Object result) {
return new RespBean(200, message, result);
}
/**
* 失败返回 (提示)
* @param message
* @return
*/
public static RespBean error(String message) {
return new RespBean(500, message, null);
}
public static RespBean warning(String message) {
return new RespBean(500, message, null);
}
}
重写方法:
@Override
@JsonDeserialize(using = CustomAuthorityDeserializer.class)
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
@RestController
@CrossOrigin
public class LoginController {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private IAdminService adminService;
@Autowired
private PasswordEncoder passwordEncoder; //密码使用对称密码加密 判断密码是否一致 需要注入在 SecurityConfig 配置类中
@Value("${jwt.tokenHead}") // 根据value注解拿到yml中设置的token头
private String tokenHead;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public RespBean login(@RequestBody Map loginForm, HttpServletRequest request) throws IOException {
Map<String,Object> result = new HashMap<>();
System.out.println(loginForm);
String username = loginForm.get("username").toString();
//是否可以用
if (!adminService.getAdminByUserName(username).isEnabled()){
System.out.println();
return RespBean.warning("账号已经禁用,请联系管理员! ");
}
String password = loginForm.get("password").toString();
String code = loginForm.get("code").toString();
Object code1 = request.getSession().getAttribute("captcha");
// 登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username); // (查库)
if (!StringUtils.isEmpty(code1) && code1.equals(code)) {
if (userDetails!=null && !passwordEncoder.matches(password, userDetails.getPassword())) {
System.out.println("200");
// 更新security登录用户对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new
UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());//第二个参数为口令(密码)不用传
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
//生成token
String token = jwtTokenUtil.generateToken(userDetails);
result.put("token", token);
result.put("tokenHead", tokenHead);
return RespBean.success("登录成功", result);
} else {
System.out.println("401");
return RespBean.warning("用户名或密码错误");
}
} else {
System.out.println("403");
return RespBean.warning("验证码错误,请重新输入");
}
}
}
spring的完全框架
maven
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
<version>2.5.5version>
dependency>
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
IAdminService adminService;
@Autowired
private DataSource dataSource;
@Autowired
private CustomUrlDecisionManger customUrlDecisionManger;
@Autowired
private CustomFilter customFilter;
@Autowired
private RestAuthorizationEnrtyPoint restAuthorizationEnrtyPoint;
//放行一些路径,且不走拦截链
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/login",
"/logout",
"/css/**",
"/js/**",
"/index.html",
"/favicon.ico",
"/doc.html",
"/webjars/**",
"/swagger-resources/**",
"/v2/api-docs/**",
"/captcha",
"/export/**",
"/import/**",
"/ws/**"
);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf防火墙,使用jwt
http.csrf().disable()
// 基于token, 不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 允许登录访问
// .antMatchers("/login", "/logout").permitAll()
// 除了上面的请求,其他的都要认证
.anyRequest()
.authenticated()
// 动态权限配置
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(customUrlDecisionManger);
o.setSecurityMetadataSource(customFilter);
return o;
}
})
.and()
// 禁用缓存
.headers()
.cacheControl();
// // 记住我,
// http.rememberMe()
// .tokenRepository(persistentTokenRepository())
// .rememberMeParameter("rememberMe")
// // .tokenValiditySeconds() 修改默认记住我的时间,默认为两周
// .userDetailsService(userDetailsService());
// 添加 JWT 登录授权过滤器
http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加自定义未授权和未登录结果的返回
http.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandel())
.authenticationEntryPoint(restAuthorizationEnrtyPoint);
}
@Override
@Bean
public UserDetailsService userDetailsService() { // 重写的了返回的参数(Admin)
return username -> {
Admin admin = adminService.getAdminByUserName(username);
if (admin != null) {
// 设置一下权限
admin.setRoles(adminService.getRoles(admin.getId()));
return admin;
}
// String password = passwordEncoder().encode("123");
// 设置权限
throw new UsernameNotFoundException("用户名或密码不正确");
};
}
//passwordEncoder使用BCryptPasswordEncoder加密器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//设置数据源
jdbcTokenRepository.setDataSource(dataSource);
//自动建表,第一次启动时开启,之后需要注释掉
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Bean
public JwtAuthencationTokenFilter jwtAuthencationTokenFilter() {
return new JwtAuthencationTokenFilter();
}
@Bean
public RestfulAccessDeniedHandel restfulAccessDeniedHandel() {
return new RestfulAccessDeniedHandel();
}
}
JwtAuthencationTokenFilter
/**
* jwt 授权登录器
*/
public class JwtAuthencationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
// 前置拦截
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader); // 获取token值
// 存在token (获取的token不为空,且token前面携带的表示是我们设置的标识)
if (authHeader != null && authHeader.startsWith(tokenHead)){
String authToken = authHeader.substring(tokenHead.length());// 截取下除了头部标识以后的token
String username = jwtTokenUtil.getUserNameFromToken(authToken); // 通过token就可以拿到用户名(解析荷载部分)
// 看看username存在,但是未登录(就是检测是不是设置在security全局中)
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
//登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 验证token是否有效,重新设置用户对象
if (jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
// 重新设置到用户对象中
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
//放行
filterChain.doFilter(request,response);
}
}
/**
* 当未登录或token失效的时候访问接口时,返回的自定义结果
*/
@Controller
public class RestAuthorizationEnrtyPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter printWriter = response.getWriter();
RespBean respBean = RespBean.error("登录失效,请重新登录");
respBean.setCode(401);
printWriter.write(new ObjectMapper().writeValueAsString(respBean));
printWriter.flush(); // 强推到浏览器
printWriter.close();
}
}
/**
* 当访问接口没有权限的时候,自定义返回结果
*/
public class RestfulAccessDeniedHandel implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter printWriter = response.getWriter();
RespBean respBean = RespBean.error("权限不足,请联系管理员");
respBean.setCode(403);
printWriter.write(new ObjectMapper().writeValueAsString(respBean));
printWriter.flush(); // 强推到浏览器
printWriter.close();
}
}
根据请求url,分析请求所需角色
/**
* 权限控制
* 根据请求url,分析请求所需角色
*/
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private IMenuService menuService;
// 匹配url的实例
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
// 获取请求的Url
String requestUrl = ((FilterInvocation) o).getRequestUrl();
// 获取菜单信息
List<Menu> menuList = menuService.getMenusWithRoles();
for (Menu menu : menuList) {
// 逐个与url进行匹配
if (antPathMatcher.match(menu.getUrl(),requestUrl)) { //第一个是匹配规则,位置不要颠倒 **
// 如果匹配成功(把这个路径对应的角色封装到一个数组)
String[] strArray = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
return SecurityConfig.createList(strArray);
}
}
// 如果匹配不上,就默认返回登录权限的路径
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
判断用户角色
/**
* 权限控制
* 判断用户角色
*/
@Component
public class CustomUrlDecisionManger implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : collection) {
// 当前url所需要的角色
String needRole = configAttribute.getAttribute();
// 判断角色是否登录即可访问的角色,此角色在CustomFilter中设置
if("ROLE_LOGIN".equals(needRole)) {
// 判断是否登录了
if(authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("尚未登录,请登录");
}else {
return;
}
}
// 判断角色是否为url所需要的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}