SpringSecurity授权

工具类

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;
    }
}

修改UserDetailsServiceloadUserByUsername方法,封装授权信息

@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<String> list = new ArrayList<> (Arrays.asList("test"));
    return new LoginUser (user, permissionKeyList);
}

修改JwtAuthenticationTokenFilterdoFilterInternal方法,


 @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

// 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();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值