import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WebUtils {
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try {
response.setStatus (200);
response.setContentType ("application/json");
response.setCharacterEncoding ("utf-8");
response.getWriter ().print (string);
} catch (IOException e) {
e.printStackTrace ();
}
return null;
}
}
启动类加注解**@EnableGlobalMethodSecurity(prePostEnabled = true)**
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
@MapperScan("com.example.security.mapper")
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run (SecurityApplication.class, args);
}
}
接口前添加**@PreAuthorize**注解,需要xxx权限才可以执行
@RestController
public class HelloController {
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('test')")
public String hello(){
return "hello";
}
}
封装 权限信息
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
//存储权限信息
private List<String> permissions;
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
//存储SpringSecurity所需要的权限信息的集合
@JSONField(serialize = false)
// 不序列化到 redis
private List<GrantedAuthority> authorities;
@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 ();
}
@Override
public String getUsername() {
return user.getUserName ();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
修改UserDetailsService的loadUserByUsername方法,封装授权信息
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<> ();
queryWrapper.eq (User::getUserName, username);
User user = userMapper.selectOne (queryWrapper);
if (user == null) {
// 用户不存在
throw new RuntimeException ("用户不存在");
}
// TODO 根据用户查询权限信息 添加到 LoginUser中
List<String> permissionKeyList = menuMapper.selectPermsByUserId (user.getId ());
// 测试写法
// List list = new ArrayList<> (Arrays.asList("test"));
return new LoginUser (user, permissionKeyList);
}
修改JwtAuthenticationTokenFilter的doFilterInternal方法,
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 对于需要登录的接口进行拦截
* 看看用户信息是否存在
*
* @param request
* @param response
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取 token
String token = request.getHeader ("token");
if (!StringUtils.hasText (token)) {
// 不携带token,放行
filterChain.doFilter (request, response);
return;
}
//解析token
String userId;
try {
Claims claims = JwtUtil.parseJWT (token);
userId = claims.getSubject ();
} catch (Exception e) {
e.printStackTrace ();
throw new RuntimeException ("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userId;
LoginUser loginUser = JSONObject.parseObject (redisTemplate.opsForValue ().get (redisKey), LoginUser.class);
if (Objects.isNull (loginUser)) {
// 没有token,就是未登录
throw new RuntimeException ("用户未登录");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken (loginUser, null, loginUser.getAuthorities ());
SecurityContextHolder.getContext ().setAuthentication (authenticationToken);
// 放行
filterChain.doFilter (request, response);
}
认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理
授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可
/**
* 授权失败
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseResult result = new ResponseResult (HttpStatus.FORBIDDEN.value (), "权限不足");
String json = JSON.toJSONString (result);
WebUtils.renderString (response, json);
}
}
/**
* 认证失败
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseResult result = new ResponseResult (HttpStatus.UNAUTHORIZED.value (), "认证失败请重新登录");
String json = JSON.toJSONString (result);
WebUtils.renderString (response, json);
}
}
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
①先对SpringBoot配置,运行跨域请求
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping ("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns ("*")
// 是否允许cookie
.allowCredentials (true)
// 设置允许的请求方式
.allowedMethods ("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders ("*")
// 跨域允许时间
.maxAge (3600);
}
}
②开启SpringSecurity的跨域访问
//允许跨域
http.cors();
// hasAnyAuthority 有其中的任何一个权限
@PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')")
public String hello(){
return "hello";
}
import com.example.security.vo.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("ex")
public class XDExpressionRoot {
public boolean hasAuthority(String authority) {
// 获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext ().getAuthentication ();
LoginUser loginUser = (LoginUser) authentication.getPrincipal ();
List<String> permissions = loginUser.getPermissions ();
// 判断用户权限集合中 是否存在authority
return permissions.contains (authority);
}
}
@ex代表这个Bean对象
@PostMapping("/user/login")
@PreAuthorize("@ex.hasAuthority('xd')")
public ResponseResult login(@RequestBody User user) {
return loginServcie.login (user);
}
在配置类中使用配置的方式 对资源进行权限控制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
.antMatchers("/testCors").hasAuthority("system:dept:list")
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 配置异常处理器
http.exceptionHandling()
// 配置认证失败处理器
.authenticationEntryPoint(authenticationEntryPoint)
// 配置授权失败处理器
.accessDeniedHandler(accessDeniedHandler);
// 允许跨域
http.cors();
}