Appearance
上文我们完成了登录认证操作,但是并没有进行权限配置,接下来我们来完成授权操作,首先在配置类开启注解鉴权。
java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// ……
}
LoginUser对象填充授权码
还记得LoginUser对象中的getAuthorities吗?这里存放的就是我们的权限码,我们来简单修改一下LoginUser对象
java
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
//存储权限信息
private List<String> permissions;
//存储SpringSecurity所需要的权限信息的集合
private List<GrantedAuthority> authorities;
public LoginUser(User user,List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
public LoginUser(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
//把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {return user.getPassword();}
// 和之前一样,此处省略……
}
在UserDetailsServiceImpl中给LoginUser对象赋权限值
java
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
User user = findUserByUsername(username);
log.info("user:{}", user);
log.info("username:{}", username);
// 如果找不到用户则抛出异常
if (user == null) {
throw new RuntimeException("用户名或密码错误");
}
//TODO 根据用户查询权限信息 添加到LoginUser中
List<String> authList = new ArrayList<>(Arrays.asList("user:add", "user:search"));
return new LoginUser(user, authList);
}
}
修改JWT工具包
jwt生成令牌和解析令牌都需要添加权限信息,生成令牌在登陆时调用,解析令牌在Token校验过滤器中调用
java
public class JwtUtils {
// 密钥
private static final byte[] TOKEN_KEY = "xk857.com".getBytes();
// token过期时间一周
private static final long EXPIRE = 60000 * 60 * 24 * 7;
/**
* 根据用户信息,生成令牌
*
* @param loginUser 用户对象
* @return token
*/
public static String geneJsonWebToken(LoginUser loginUser) {
// 默认使用HS256加密算法
return JWT.create()
.setPayload("id", loginUser.getUser().getId())
.setPayload("username", loginUser.getUser().getUsername())
.setPayload("nickName", loginUser.getUser().getNickName())
.setPayload("permissions", loginUser.getPermissions())
.setIssuedAt(new Date())
.setExpiresAt(new Date(System.currentTimeMillis() + EXPIRE))
.setKey(TOKEN_KEY).sign();
}
/**
* 解析Token
*/
public static LoginUser checkJWT(String token) {
// 验证Token
boolean validate = JWT.of(token).setKey(TOKEN_KEY).validate(0);
if (!validate) {
// 企业开发建议使用自定义异常
throw new RuntimeException("Token过期");
}
JWT jwt = JWT.of(token);
User user = new User();
user.setId((String) jwt.getPayload("id"));
user.setUsername((String) jwt.getPayload("username"));
user.setNickName((String) jwt.getPayload("nickName"));
List<String> permissions = (List<String>) jwt.getPayload("permissions");
return new LoginUser(user, permissions);
}
}
登录接口存储权限信息
生成的Token需要存储权限信息,因为解析的时候需要使用。
java
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/user/login")
public Map<String,String> login(@RequestBody LoginParam param) {
// 使用SpringSecurity登录认证,通用写法
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(param.getUsername(),param.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){
throw new RuntimeException("账号或密码错误");
}
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
// 生成token
String token = JwtUtils.geneJsonWebToken(loginUser);
Map<String,String> map = new HashMap<>();
map.put("token",token);
return map;
}
}
修改Token校验过滤器
之前我们仅设置了用户的登录信息,SecurityContextHolder中并没有存储权限码
java
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token,获取User对象封装成LoginUser对象
User user = JwtUtils.checkJWT(token);
LoginUser loginUser = new LoginUser(user);
//存入SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); // 注意这一行,仅修改了这一行
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
开发测试接口
java
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
@PreAuthorize("hasAuthority('user:add')")
public String add() {
return "添加用户";
}
@PutMapping
@PreAuthorize("hasAuthority('user:update')")
public String update() {
return "更新用户信息";
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('user:del')")
public String add(@PathVariable String id) {
return "删除用户:"+id;
}
@GetMapping("/test")
@PreAuthorize("hasAuthority('user:add') and hasAuthority('user:update')")
public String addAndUpdate() {
return "同时添加和更新用户";
}
}
Postman接口测试
调用登录接口获取Token
测试添加接口,接口正常返回数据
测试删除接口提示权限不足