JWT

1.介绍:

全称 JSON Web Token,通过数字签名的方式,以JSON为载体,在不同的服务终端之间安全的传递信息。

常用于授权认证,用户登录后的每个请求都包含JWT,后端处理请求之前都要进行校验。

2.组成:

Header:数据头,令牌类型和加密算法

Payload:负载,请求体和其他数据

Signature:签名,把头部的base64UrlEncode与负载的base64UrlEncode拼接起来再进行HMACSHA256加密

用户认证流程

1.用户提交登录表单(用户名和密码)

2.后端校验成功后生成JWT,通过response的header返回给前端

3.前端将JWT保存到LocalStorage中

4.之后所有的请求中请求头都携带JWT进行身份认证

Spring Security(安全框架)

1、介绍 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。

如果项目中需要进行权限管理,具有多个角色和多种权限,我们可以使用Spring Security。

采用的是责任链的设计模式,是一堆过滤器链的组合,它有一条很长的过滤器链。

2、功能 Authentication (认证),就是用户登录 Authorization (授权),判断用户拥有什么权限,可以访问什么资源 安全防护,跨站脚本攻击,session攻击等 非常容易结合Spring进行使用

3、Spring Security与Shiro的区别

优点:

1、Spring Security基于Spring开发,项目如果使用Spring作为基础,配合Spring Security做权限更加方便。而Shiro需要和Spring进行整合开发 2、Spring Security功能比Shiro更加丰富,例如安全防护方面 3、Spring Security社区资源相对比Shiro更加丰富

缺点:

1)Shiro的配置和使用比较简单,Spring Security上手复杂些 2)Shiro依赖性低,不需要依赖任何框架和容器,可以独立运行。Spring Security依赖Spring容器

需要实现的过滤器和处理器

1、LogoutSuccessHandler:   表示登出处理器 2、验证码过滤器Filter 3、登录认证成功、失败处理器 4、BasicAuthenticationFilter:   该过滤器用于普通http请求进行身份认证 5、AuthenticationEntryPoint:   表示认证失败处理器 6、AccessDenieHandler:   用户发起无权限访问请求的处理器 7、UserServiceDatils 接口:   该接口十分重要,用于从数据库中验证用户名密码 8、PasswordEncoder密码验证器

整合

1.添加相应依赖

org.springframework.boot

spring-boot-starter-security

org.springframework.boot

spring-boot-starter-data-redis

io.jsonwebtoken

jjwt

0.9.1

com.github.axet

kaptcha

0.0.9

cn.hutool

hutool-all

5.3.3

org.apache.commons

commons-lang3

3.11

org.apache.commons

commons-lang3

commons-codec

commons-codec

1.15

org.springframework.boot

spring-boot-starter-validation

2.写一个JWT工具类(生成JWT、解析JWT、判断JWT是否过期)

import io.jsonwebtoken.*;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

import java.util.Date;

@Data

@Component

@ConfigurationProperties(prefix = "xiaolinbao.jwt")

public class JwtUtils {

//使用@ConfigurationProperties注解可以读取配置文件中的信息,只要在 Bean 上添加上了这个注解,指定好配置文件中的前缀,那么对应的配置文件数据就会自动填充到 Bean 的属性中

private long expire;

private String secret;

private String header;

// 生成JWT

public String generateToken(String username) {

Date nowDate = new Date();

Date expireDate = new Date(nowDate.getTime() + 1000 * expire);

return Jwts.builder()

.setHeaderParam("typ", "JWT")

.setSubject(username)

.setIssuedAt(nowDate)

.setExpiration(expireDate) // 7天过期

.signWith(SignatureAlgorithm.HS512, secret)

.compact();

}

// 解析JWT

public Claims getClaimsByToken(String jwt) {

try {

return Jwts.parser()

.setSigningKey(secret)

.parseClaimsJws(jwt)

.getBody();

} catch (Exception e) {

return null;

}

}

// 判断JWT是否过期

public boolean isTokenExpired(Claims claims) {

return claims.getExpiration().before(new Date());

}

}

#JWT配置

xiaolinbao:

jwt:

header: Authorization

expire: 604800 # 7天,s为单位

secret: abcdefghabcdefghabcdefghabcdefgh

封装Result

import lombok.Data;

import java.io.Serializable;

@Data

public class Result implements Serializable {

private int code;

private String msg;

private Object data;

public static Result succ(Object data) {

return succ(200, "操作成功", data);

}

public static Result fail(String msg) {

return fail(400, msg, null);

}

public static Result succ (int code, String msg, Object data) {

Result result = new Result();

result.setCode(code);

result.setMsg(msg);

result.setData(data);

return result;

}

public static Result fail (int code, String msg, Object data) {

Result result = new Result();

result.setCode(code);

result.setMsg(msg);

result.setData(data);

return result;

}

}

LoginSuccessHandler(登录成功处理器)实现AuthenticationSuccessHandler

@Component

public class LoginSuccessHandler implements AuthenticationSuccessHandler {

@Autowired

JwtUtils jwtUtils;

@Override

public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

httpServletResponse.setContentType("application/json;charset=UTF-8");

ServletOutputStream outputStream = httpServletResponse.getOutputStream();

// 生成JWT,并放置到请求头中

String jwt = jwtUtils.generateToken(authentication.getName());

httpServletResponse.setHeader(jwtUtils.getHeader(), jwt);

Result result = Result.succ("SuccessLogin");

outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));

outputStream.flush();

outputStream.close();

}

}

LoginFailureHandler(登录失败处理器)实现AuthenticationFailureHandler

@Component

public class LoginFailureHandler implements AuthenticationFailureHandler {

@Override

public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

httpServletResponse.setContentType("application/json;charset=UTF-8");

ServletOutputStream outputStream = httpServletResponse.getOutputStream();

String errorMessage = "用户名或密码错误";

Result result;

if (e instanceof CaptchaException) {

errorMessage = "验证码错误";

result = Result.fail(errorMessage);

} else {

result = Result.fail(errorMessage);

}

outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));

outputStream.flush();

outputStream.close();

}

}

自定义的验证码异常

public class CaptchaException extends AuthenticationException {

public CaptchaException(String msg) {

super(msg);

}

}

验证码工具类

@Configuration

public class KaptchaConfig {

@Bean

DefaultKaptcha producer() {

Properties properties = new Properties();

properties.put("kaptcha.border", "no");

properties.put("kaptcha.textproducer.font.color", "black");

properties.put("kaptcha.textproducer.char.space", "4");

properties.put("kaptcha.image.height", "40");

properties.put("kaptcha.image.width", "120");

properties.put("kaptcha.textproducer.font.size", "30");

Config config = new Config(properties);

DefaultKaptcha defaultKaptcha = new DefaultKaptcha();

defaultKaptcha.setConfig(config);

return defaultKaptcha;

}

}

验证码 Controller

@Autowired

Producer producer;

@GetMapping("/captcha")

public Result Captcha() throws IOException {

String key = UUID.randomUUID().toString();

String code = producer.createText();

BufferedImage image = producer.createImage(code);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

ImageIO.write(image, "jpg", outputStream);

BASE64Encoder encoder = new BASE64Encoder();

String str = "data:image/jpeg;base64,";

String base64Img = str + encoder.encode(outputStream.toByteArray());

//随机码为key,验证码为value

redisUtil.hset(Const.CAPTCHA_KEY, key, code, 120);

return Result.succ(

MapUtil.builder()

.put("userKey", key)

.put("captcherImg", base64Img)

.build()

);

}

验证码过滤器CaptchaFilter

@Component

public class CaptchaFilter extends OncePerRequestFilter {

@Autowired

RedisUtil redisUtil;

@Autowired

LoginFailureHandler loginFailureHandler;

//自定义处理逻辑

@Override

protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

String url = httpServletRequest.getRequestURI();

if ("/login".equals(url) && httpServletRequest.getMethod().equals("POST")) {

// 校验验证码

try {

validate(httpServletRequest);

} catch (CaptchaException e) {

// 交给认证失败处理器

loginFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);

}

}

filterChain.doFilter(httpServletRequest, httpServletResponse);

}

// 校验验证码逻辑

private void validate(HttpServletRequest httpServletRequest) {

String code = httpServletRequest.getParameter("code");

String key = httpServletRequest.getParameter("userKey");

if (StringUtils.isBlank(code) || StringUtils.isBlank(key)) {

throw new CaptchaException("验证码错误");

}

if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY, key))) {

throw new CaptchaException("验证码错误");

}

// 若验证码正确,执行以下语句

// 一次性使用

redisUtil.hdel(Const.CAPTCHA_KEY, key);

}

}

JWT过滤器JwtAuthenticationFilter

检验JWT是否正确以及是否过期

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

@Autowired

JwtUtils jwtUtils;

@Autowired

UserDetailServiceImpl userDetailService;

@Autowired

SysUserService sysUserService;

public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {

super(authenticationManager);

}

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

String jwt = request.getHeader(jwtUtils.getHeader());

// 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的

// 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口

if (StrUtil.isBlankOrUndefined(jwt)) {

chain.doFilter(request, response);

return;

}

Claims claim = jwtUtils.getClaimsByToken(jwt);

if (claim == null) {

throw new JwtException("token 异常");

}

if (jwtUtils.isTokenExpired(claim)) {

throw new JwtException("token 已过期");

}

String username = claim.getSubject();

// 获取用户的权限等信息

SysUser sysUser = sysUserService.getByUsername(username);

// 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getId()));

SecurityContextHolder.getContext().setAuthentication(token);

chain.doFilter(request, response);

}

}

SecurityContextHolder.getContext().getAuthentication().getPrincipal()等方法获取到当前登录的用户信息

JWT认证失败处理器JwtAuthenticationEntryPoint

@Component

public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

//认证失败的处理

@Override

public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

httpServletResponse.setContentType("application/json;charset=UTF-8");

httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

ServletOutputStream outputStream = httpServletResponse.getOutputStream();

Result result = Result.fail("请先登录");

outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));

outputStream.flush();

outputStream.close();

}

}

从数据库中验证用户名、密码:UserServiceDetails、AuthenticationManager、UserDetails

SpringSecurity中的认证管理器AuthenticationManager是一个抽象接口,用以提供各种认证方式。一般我们都使用从数据库中验证用户名、密码是否正确这种认证方式。

AuthenticationManager的默认实现类是ProviderManager,ProviderManager提供很多认证方式,DaoAuthenticationProvider是AuthenticationProvider的一种实现,可以通过实现UserDetailsService接口的方式来实现数据库查询方式登录。

Spring Security在拿到UserDetails之后,会去对比Authentication(Authentication如何得到?我们使用的是默认的UsernamePasswordAuthenticationFilter,它会读取表单中的用户信息并生成Authentication),若密码正确,则Spring Secuity自动帮忙完成登录

定义一个UserDetails接口的实现类,称为AccountUser实现所有方法

public interface UserDetails extends Serializable {

//获取用户权限

Collection getAuthorities();

//用户密码

String getPassword();

//用户名

String getUsername();

//用户是否过期

boolean isAccountNonExpired();

//用户是否被锁定

boolean isAccountNonLocked();

//认证信息是否过期

boolean isCredentialsNonExpired();

//用户启用还是禁用

boolean isEnabled();

}

实现 UserDetails (默认有权限管理功能)

public class AccountUser implements UserDetails {

private Long userId;

private static final long serialVersionUID = 540L;

private static final Log logger = LogFactory.getLog(User.class);

private String password;

private final String username;

private final Collection authorities;

private final boolean accountNonExpired;

private final boolean accountNonLocked;

private final boolean credentialsNonExpired;

private final boolean enabled;

public AccountUser(Long userId, String username, String password, Collection authorities) {

this(userId, username, password, true, true, true, true, authorities);

}

public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) {

Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");

this.userId = userId;

this.username = username;

this.password = password;

this.enabled = enabled;

this.accountNonExpired = accountNonExpired;

this.credentialsNonExpired = credentialsNonExpired;

this.accountNonLocked = accountNonLocked;

this.authorities = authorities;

}

@Override

public Collection getAuthorities() {

return this.authorities;

}

@Override

public String getPassword() {

return this.password;

}

@Override

public String getUsername() {

return this.username;

}

@Override

public boolean isAccountNonExpired() {

return this.accountNonExpired;

}

@Override

public boolean isAccountNonLocked() {

return this.accountNonLocked;

}

@Override

public boolean isCredentialsNonExpired() {

return this.credentialsNonExpired;

}

@Override

public boolean isEnabled() {

return this.enabled;

}

}

实现 UserDetailsService 重写其loadUserByUsername方法 使用用户名在数据库中查找用户信息返回

@Service

public class UserDetailServiceImpl implements UserDetailsService {

@Autowired

SysUserService sysUserService;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

SysUser sysUser = sysUserService.getByUsername(username);

if (sysUser == null) {

throw new UsernameNotFoundException("用户名或密码错误");

}

return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));

}

/**

* 获取用户权限信息(角色、菜单权限)

* @param userId

* @return

*/

public List getUserAuthority(Long userId) {

// 实际怎么写以数据表结构为准,这里只是写个例子

// 角色(比如ROLE_admin),菜单操作权限(比如sys:user:list)

String authority = sysUserService.getUserAuthorityInfo(userId); // 比如ROLE_admin,ROLE_normal,sys:user:list,...

return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);

}

}

实现了上述几个接口,从数据库中验证用户名、密码的过程将由框架帮我们完成,封装隐藏了

无权限访问的处理:AccessDenieHandler

当权限不足时,我们需要设置权限不足状态码403,并将错误信息返回给前端

@Component

public class JwtAccessDeniedHandler implements AccessDeniedHandler {

@Override

public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {

httpServletResponse.setContentType("application/json;charset=UTF-8");

httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);

ServletOutputStream outputStream = httpServletResponse.getOutputStream();

Result result = Result.fail(e.getMessage());

outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));

outputStream.flush();

outputStream.close();

}

}

登出处理器 LogoutSuccessHandler

1.将原来的 JWT 置为空返给前端

2.用空字符串覆盖之前的 JWT (JWT是无状态 无法销毁 只能等过期 所以采用置空浏览器中保存的JWT)

3.清除SecurityContext中的用户信息 (通过创建SecurityContextLogoutHandler对象,调用它的logout方法)

实现 LogoutSuccessHandler 重写 onLogoutSuccess 方法

@Component

public class JWTLogoutSuccessHandler implements LogoutSuccessHandler {

@Autowired

JwtUtils jwtUtils;

@Override

public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

if (authentication != null) {

new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);

}

httpServletResponse.setContentType("application/json;charset=UTF-8");

ServletOutputStream outputStream = httpServletResponse.getOutputStream();

httpServletResponse.setHeader(jwtUtils.getHeader(), "");

Result result = Result.succ("SuccessLogout");

outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));

outputStream.flush();

outputStream.close();

}

}

密码加密解密:PasswordEncoder

1.首先前端对密码进行esa加密

2.后端对前端传输过来的密码进行解密

3.再根据数据库的加密规则BCrypt进行加密

SpringSecurity提供了用于密码加密解密的工具类BCryptPasswordEncoder

需自定义PasswordEncoder类,并使其继承BCryptPasswordEncoder,重写其matches方法

@NoArgsConstructor

public class PasswordEncoder extends BCryptPasswordEncoder {

//判断从前端接收的密码与数据库中的密码是否一致

@Override

public boolean matches(CharSequence rawPassword, String encodedPassword) {

// 接收到的前端的密码

String pwd = rawPassword.toString();

// 进行rsa解密

try {

pwd = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, pwd);

} catch (Exception e) {

throw new BadCredentialsException(e.getMessage());

}

if (encodedPassword != null && encodedPassword.length() != 0) {

return BCrypt.checkpw(pwd, encodedPassword);

} else {

return false;

}

}

}

Spring Security全局配置:SecurityConfig

需要继承WebSecurityConfigurerAdapter(采用适配器模式,继承后SecurityConfig可以看做是WebSecurityConfigurer)

SecurityConfig需要使用@EnableGlobalMethodSecurity(prePostEnabled = true)注解

Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,来判断用户对某个控制层的方法是否具有访问权限。prePostEnabled = true即可在方法前后进行权限检查

Security内置的权限注解如下:   @PreAuthorize:方法执行前进行权限检查 @PreAuthorize("hasAuthority('sys:user:list')")   @PostAuthorize:方法执行后进行权限检查   @Secured:类似于 @PreAuthorize   可以在Controller的方法前添加这些注解表示接口需要什么权限。

配置类还需使用@EnableWebSecurity注解,该注解有两个作用:1. 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2.加载了AuthenticationConfiguration, 配置了认证信息。AuthenticationConfiguration这个类的作用就是用来创建ProviderManager。   @EnableWebSecurity完成的工作便是加载了WebSecurityConfiguration,AuthenticationConfiguration这两个核心配置类,也就此将spring security的职责划分为了配置安全信息,配置认证信息两部分。

在SecurityConfig这个配置类中,我们需要将之前写的拦截器和处理器都autowire进来,并使用@Bean注解,声明JwtAuthenticationFilter和PasswordEncoder的构造函数。在JwtAuthenticationFilter的构造函数中,我们调用authenticationManager()方法给JwtAuthenticationFilter提供AuthenticationManager。

配置类需要重写configure方法进行配置,该方法有多种重载形式,我们使用其中的两种,其中一个用于配置url安全拦截配置,另一个用于AuthenticationManager配置UserDetailsService的实现类

@Configuration

@EnableWebSecurity

@RequiredArgsConstructor

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired

LoginFailureHandler loginFailureHandler;

@Autowired

LoginSuccessHandler loginSuccessHandler;

@Autowired

CaptchaFilter captchaFilter;

@Autowired

JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Autowired

JwtAccessDeniedHandler jwtAccessDeniedHandler;

@Autowired

UserDetailServiceImpl userDetailService;

@Autowired

JWTLogoutSuccessHandler jwtLogoutSuccessHandler;

@Bean

JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {

JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());

return jwtAuthenticationFilter;

}

private static final String[] URL_WHITELIST = {

"/login",

"/logout",

"/captcha",

"/favicon.ico"

};

@Bean

PasswordEncoder PasswordEncoder() {

return new PasswordEncoder();

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http.cors().and().csrf().disable()

// 登录配置

.formLogin()

.successHandler(loginSuccessHandler)

.failureHandler(loginFailureHandler)

.and()

.logout()

.logoutSuccessHandler(jwtLogoutSuccessHandler)

// 禁用session

.and()

.sessionManagement()

.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

// 配置拦截规则

.and()

.authorizeRequests()

.antMatchers(URL_WHITELIST).permitAll()

.anyRequest().authenticated()

// 异常处理器

.and()

.exceptionHandling()

.authenticationEntryPoint(jwtAuthenticationEntryPoint)

.accessDeniedHandler(jwtAccessDeniedHandler)

// 配置自定义的过滤器

.and()

.addFilter(jwtAuthenticationFilter())

// 验证码过滤器放在UsernamePassword过滤器之前

.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)

;

}

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailService);

}

}

自定义权限校验注解

Spring Security提供了Spring EL表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限。

@PreAuthorize 注解用于配置接口要求用户拥有某些权限才可访问

方法参数描述hasPermiString验证用户是否具备某权限lacksPermiString验证用户是否不具备某权限,与 hasPermi逻辑相反hasAnyPermiString验证用户是否具有以下任意一个权限hasRoleString判断用户是否拥有某个角色lacksRoleString验证用户是否不具备某角色,与 isRole逻辑相反hasAnyRolesString验证用户是否具有以下任意一个角色,多个逗号分隔

使用 @ss 代表 PermissionService(许可服务) 类,对每个接口拦截并调用PermissionService的对应方法判断接口调用者的权限。

package com.example.framework.web.service;

import java.util.Set;

import org.springframework.stereotype.Service;

import org.springframework.util.CollectionUtils;

import com.example.common.core.domain.entity.SysRole;

import com.example.common.core.domain.model.LoginUser;

import com.example.common.utils.SecurityUtils;

import com.example.common.utils.StringUtils;

import com.example.framework.security.context.PermissionContextHolder;

/**

* 自定义权限实现,ss => SpringSecurity首字母

* 超级管理员拥有所有权限,不受权限约束。

*/

@Service("ss")

public class PermissionService

{

/** 所有权限标识 */

private static final String ALL_PERMISSION = "*:*:*";

/** 管理员角色权限标识 */

private static final String SUPER_ADMIN = "admin";

private static final String ROLE_DELIMETER = ",";

private static final String PERMISSION_DELIMETER = ",";

/**

* 验证用户是否具备某权限

*

* @param permission 权限字符串

* @return 用户是否具备某权限

*/

public boolean hasPermi(String permission)

{

if (StringUtils.isEmpty(permission))

{

return false;

}

LoginUser loginUser = SecurityUtils.getLoginUser();

if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))

{

return false;

}

PermissionContextHolder.setContext(permission);

return hasPermissions(loginUser.getPermissions(), permission);

}

/**

* 验证用户是否不具备某权限,与 hasPermi逻辑相反

*

* @param permission 权限字符串

* @return 用户是否不具备某权限

*/

public boolean lacksPermi(String permission)

{

return hasPermi(permission) != true;

}

/**

* 验证用户是否具有以下任意一个权限

*

* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表

* @return 用户是否具有以下任意一个权限

*/

public boolean hasAnyPermi(String permissions)

{

if (StringUtils.isEmpty(permissions))

{

return false;

}

LoginUser loginUser = SecurityUtils.getLoginUser();

if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))

{

return false;

}

PermissionContextHolder.setContext(permissions);

Set authorities = loginUser.getPermissions();

for (String permission : permissions.split(PERMISSION_DELIMETER))

{

if (permission != null && hasPermissions(authorities, permission))

{

return true;

}

}

return false;

}

/**

* 判断用户是否拥有某个角色

*

* @param role 角色字符串

* @return 用户是否具备某角色

*/

public boolean hasRole(String role)

{

if (StringUtils.isEmpty(role))

{

return false;

}

LoginUser loginUser = SecurityUtils.getLoginUser();

if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))

{

return false;

}

for (SysRole sysRole : loginUser.getUser().getRoles())

{

String roleKey = sysRole.getRoleKey();

if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))

{

return true;

}

}

return false;

}

/**

* 验证用户是否不具备某角色,与 isRole逻辑相反。

*

* @param role 角色名称

* @return 用户是否不具备某角色

*/

public boolean lacksRole(String role)

{

return hasRole(role) != true;

}

/**

* 验证用户是否具有以下任意一个角色

*

* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表

* @return 用户是否具有以下任意一个角色

*/

public boolean hasAnyRoles(String roles)

{

if (StringUtils.isEmpty(roles))

{

return false;

}

LoginUser loginUser = SecurityUtils.getLoginUser();

if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))

{

return false;

}

for (String role : roles.split(ROLE_DELIMETER))

{

if (hasRole(role))

{

return true;

}

}

return false;

}

/**

* 判断是否包含权限

*

* @param permissions 权限列表

* @param permission 权限字符串

* @return 用户是否具备某权限

*/

private boolean hasPermissions(Set permissions, String permission)

{

return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));

}

}

数据权限示例

// 符合system:user:list权限要求

@PreAuthorize("@ss.hasPermi('system:user:list')")

// 不符合system:user:list权限要求

@PreAuthorize("@ss.lacksPermi('system:user:list')")

// 符合system:user:add或system:user:edit权限要求即可

@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")

角色权限示例

// 属于user角色

@PreAuthorize("@ss.hasRole('user')")

// 不属于user角色

@PreAuthorize("@ss.lacksRole('user')")

// 属于user或者admin之一

@PreAuthorize("@ss.hasAnyRoles('user,admin')")

公开接口(不需要验证权限可以公开访问的)

使用注解方式,只需要在Controller的类或方法上加入@Anonymous该注解即可

// @PreAuthorize("@ss.xxxx('....')") 注释或删除掉原有的权限注解

@Anonymous

@GetMapping("/list")

public List list(SysXxxx xxxx)

{

return xxxxList;

}

前端

前端需要做两件事,一是登录成功后把JWT存到localStore里面,二是在每次请求之前,都在请求头中添加JWT 我们在store文件夹里创建index.js,将JWT定义为token,以及定义SET_TOKEN方法

Vue.use(Vuex)

export default new Vuex.Store({

state: {

token: ''

},

mutations: {

SET_TOKEN: (state, token) => {

state.token = token

localStorage.setItem("token", token)

},

},

actions: {

},

modules: {

}

})

在登录成功时,接收后端传来的JWT并保存

const jwt = res.headers['authorization']

this.$store.commit('SET_TOKEN', jwt)

在src文件夹下创建axios.js,进行axios配置,配置前置拦截器,为所有需要权限的请求装配上header的token信息

const request = axios.create({

timeout: 5000,

headers: {

'Content-Type': "application/json; charset=utf-8"

}

})

// 前置拦截,为所有需要权限的请求装配上header的token信息

request.interceptors.request.use(config => {

config.headers['Authorization'] = localStorage.getItem("token")

return config

})

文章来源

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。
大家都在看: