傻瓜式使用SpringSecurity完成前后端分离+JWT+登录认证+权限控制

流程分析

在这里插入图片描述
流程说明:

  1. 客户端发起一个请求,进入 Security 过滤器链。当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理。
  2. 如果不是登出路径则直接进入下一个过滤器。当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler ,登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
  3. 进入认证BasicAuthenticationFilter进行用户认证,成功的话会把认证了的结果写入到SecurityContextHolder中SecurityContext的属性authentication上面。如果认证失败就会交给AuthenticationEntryPoint认证失败处理类,或者抛出异常被后续ExceptionTranslationFilter过滤器处理异常,如果是AuthenticationException就交给AuthenticationEntryPoint处理,如果是AccessDeniedException异常则交给AccessDeniedHandler处理。
  4. 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层,否则到 AccessDeniedHandler 鉴权失败处理器处理。

ok,上面我们说的流程中涉及到几个组件,有些是我们需要根据实际情况来重写的。因为我们是使用json数据进行前后端数据交互,并且我们返回结果也是特定封装的。我们先再总结一下我们需要了解的几个组件:

  • LogoutFilter - 登出过滤器
  • logoutSuccessHandler - 登出成功之后的操作类
  • UsernamePasswordAuthenticationFilter - from提交用户名密码登录认证过滤器
  • AuthenticationFailureHandler - 登录失败操作类
  • AuthenticationSuccessHandler - 登录成功操作类
  • BasicAuthenticationFilter - Basic身份认证过滤器
  • SecurityContextHolder - 安全上下文静态工具类
  • AuthenticationEntryPoint - 认证失败入口
  • ExceptionTranslationFilter - 异常处理过滤器
  • AccessDeniedHandler - 权限不足操作类
  • FilterSecurityInterceptor - 权限判断拦截器、出口

用户认证

首先我们来解决用户认证问题,分为首次登陆,和二次认证。

  • 首次登录认证:用户名、密码和验证码完成登录
  • 二次token认证:请求头携带Jwt进行身份认证

生成验证码

导入依赖

     <!-- springBoot整合redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--图片验证码-->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>
         <!-- hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.3</version>
        </dependency>

这里使用到了Redis工具类
Redis工具类参考博客
记得配置Redis序列化规则

Redis序列化配置参考博客

数据库访问使用到了MybatisPuls
自行参考其他MP相关文档

配置验证码生成规则

@Configuration
public class KaptchaConfig {
    
    

    @Bean
    public 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;
    }

}

生成验证码

@Api(tags = "图片验证码相关")
@RestController
public class AuthController extends BaseController{
    
    

    @Autowired
    private Producer producer;

    @ApiOperation("生成图片验证码")
    @GetMapping("/captcha")
    public Result captcha() throws IOException {
    
    
        String key = "aaaaa";//UUID.randomUUID().toString();
        //生成验证码 5位数随机验证码
        String code = "11111";//producer.createText();
        //生成图片
        BufferedImage image=producer.createImage(code);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImageIO.write(image,"jpg",out);
        //转换成base64位编码
        BASE64Encoder encoder = new BASE64Encoder();
        String str="data:image/jpeg;base64,";//前缀

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

        redisUtil.hset(Const.CAPTCHA_KEY,key,code,120);
        return Result.success(
                MapUtil.builder().put("token", key)
                        .put("captchaImg", base64Img)
                        .build()
        );
    }
}

生成JWT

导入依赖

<!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

JWT 工具类

/**
 * JWT
 * @author Tu_Yooo
 * @Date 2021/5/25 15:54
 */
@Data
@Component
@ConfigurationProperties(prefix = "tutony.jwt")
public class JwtUtils {
    
    

    //过期时间
    private Long expire;

    //密钥
    private String secret;

    //JWT名称
    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());
    }

}

yml配置JWT

# JWT
tutony:
  jwt:
    header: Authorization
    expire: 604800 # 7天秒单位
    secret: f4e2e52034348f86b67cde581c0f9eb5

自定义Security登录成功或失败处理

由于我们是前后端分离项目,无论是否登录成功返回给前端都应该是一个JSON格式的数据

统一的结果集返回

/**
 * 统一的结果集返回
 * @author Tu_Yooo
 * @Date 2021/5/24 16:50
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result implements Serializable {
    
    

    //200是正常,非200表示异常
    private Integer code;
    // 结果消息
    private String msg;
    //结果数据
    private Object data;


    /调用正常时返回///
    public static Result success(Integer code,String msg,Object data){
    
    
        return new Result(code,msg,data);
    }
    public static Result success(Integer code,Object data){
    
    
        Result result = new Result();
        result.setCode(code);
        result.setData(data);
        return result;
    }
    public static Result success(Object data){
    
    
        Result result = new Result();
        result.setCode(200);
        result.setData(data);
        return result;
    }

    /异常时返回//
    public static Result fail(Integer code,String msg,Object data){
    
    
        return new Result(code,msg,data);
    }
    public static Result fail(Integer code,String msg){
    
    
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }
    public static Result fail(String msg){
    
    
        Result result = new Result();
        result.setCode(400);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }

}

登录成功失败处理

/**
 * Security 登录处理
 * AuthenticationFailureHandler 失败处理
 * AuthenticationSuccessHandler 成功处理
 * @author Tu_Yooo
 * @Date 2021/5/25 14:23
 */
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler, AuthenticationSuccessHandler {
    
    

    //登录失败时处理
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    
    
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream out = httpServletResponse.getOutputStream();
        Result fail = Result.fail(e.getMessage());
        out.write(JSONUtil.toJsonStr(fail).getBytes("UTF-8"));

        out.flush();
        out.close();
    }

    @Autowired
    private JwtUtils jwtUtils;

    //登录成功时处理
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    
    
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream out = httpServletResponse.getOutputStream();

        //生成JWT,并放置到请求头中
        String jwt = jwtUtils.generateToken(authentication.getName());
        httpServletResponse.setHeader(jwtUtils.getHeader(),jwt);

        Result fail = Result.success(jwt);

        out.write(JSONUtil.toJsonStr(fail).getBytes("UTF-8"));

        out.flush();
        out.close();
    }
}

验证码认证过滤器

此过滤器放置在Security用户密码认证过滤器之前,先一步校验验证码,如果通过,继续验证用户名密码,如果不通过则抛出异常

/**
 * 验证码认证过滤器
 * 图片验证码校验过滤器,在登录过滤器前
 * OncePerRequestFilter
 * @author Tu_Yooo
 * @Date 2021/5/25 14:55
 */
@Component
public class CaptchaFilter extends OncePerRequestFilter {
    
    

    //自定义Security登录成功或失败处理
    @Autowired
    private LoginFailureHandler loginFailureHandler;

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

        String url = httpServletRequest.getRequestURI();

        //只需要拦截 /login POST请求
        if ("/login".equals(url)&& httpServletRequest.getMethod().equals("POST")){
    
    
            try{
    
    
                //校验验证码
                vaildata(httpServletRequest);
            }catch (CaptchaException e){
    
    
                //如果不正确,就调整到认证失败处理器
                loginFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
            }
        }

        //验证成功 则继续往下面走
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    @Autowired
    private RedisUtil redisUtil;

    //校验验证码
    private void vaildata(HttpServletRequest request){
    
    
        //用户输入的验证码
        String code = request.getParameter("code");
        //验证码 在Redis中的Key
        String token = request.getParameter("token");

        if (StringUtils.isBlank(token) || StringUtils.isBlank(code)){
    
    
            throw new CaptchaException("验证码错误");
        }

       if (!redisUtil.hexists(Const.CAPTCHA_KEY, token)){
    
    
           throw new CaptchaException("验证码失效");
       }

        if(!code.equals(redisUtil.hget(Const.CAPTCHA_KEY,token))){
    
    
            throw new CaptchaException("验证码错误");
        }

        //清除redis 验证码一次性使用
        redisUtil.hdel(Const.CAPTCHA_KEY,token);
    }

}

自定义登出配置

清空JWT

/**
 * 登出操作 清空数据
 * @author Tu_Yooo
 * @Date 2021/5/27 10:15
 */
@Component
public class JWTLogoutSuccessHandler implements LogoutSuccessHandler {
    
    

    @Autowired
    private 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 out = httpServletResponse.getOutputStream();

        //生成JWT,并放置到请求头中
        httpServletResponse.setHeader(jwtUtils.getHeader(),"");

        Result fail = Result.success("登出成功");

        out.write(JSONUtil.toJsonStr(fail).getBytes("UTF-8"));

        out.flush();
        out.close();
    }
}

JWT识别过滤器

用户访问其他接口时,我们会判断是否携带JWT,从JWT中取出用户信息,查询数据库此用户关联的权限信息,一并封装到Security中

/**
 * 自定义一个过滤器用来进行识别jwt。
 * 实现自动登录
 * @author Tu_Yooo
 * @Date 2021/5/25 17:14
 */
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
    
    

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
    
    
        super(authenticationManager);
    }

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private SysUserService sysUserService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
    
        String header = request.getHeader(jwtUtils.getHeader());

        if(StrUtil.isBlankOrUndefined(header)){
    
    
            chain.doFilter(request,response);
            return;
        }
         //工具类解析JWT
        Claims token = jwtUtils.getClaimsByToken(header);

        if (token == null){
    
    
            throw new JwtException("token异常");
        }

        if (jwtUtils.isTokenExpired(token)){
    
    
            log.info("jwt过期");
            throw new JwtException("token过期");
        }

        String username = token.getSubject();

        //查询数据库-用户名关联的用户
        SysUser user = sysUserService.getByUsername(username);

        //获取用户权限等信息
        UsernamePasswordAuthenticationToken authentication =
                // 参数: 用户名 密码 权限信息
                new UsernamePasswordAuthenticationToken(username,null,userDetailsService.getUserAuthority(user.getId()));

        //后续security就能获取到当前登录的用户信息了,也就完成了用户认证。
        SecurityContextHolder.getContext().setAuthentication(authentication);

        chain.doFilter(request,response);
    }
}

这里使用UserDetailsServiceImpl查询了用户id关联的权限信息,我们写在后面

异常处理

JWT识别异常

当JWT过期或者出现异常时会交给此过滤器进行统一处理

/**
 * JWT识别异常处理
 * @author Tu_Yooo
 * @Date 2021/5/26 9:53
 */
@Slf4j
@Component
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    
    
        log.error("认证失败!未登录!");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//401
        ServletOutputStream out = httpServletResponse.getOutputStream();

        Result fail = Result.fail("请先登录!");
        out.write(JSONUtil.toJsonStr(fail).getBytes("UTF-8"));

        out.flush();
        out.close();

    }
}

其他异常处理

/**
 * 其他异常处理
 * @author Tu_Yooo
 * @Date 2021/5/26 10:01
 */
@Slf4j
@Component
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
    
    
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    
    
        log.info("权限不够!!");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);//403
        ServletOutputStream out = httpServletResponse.getOutputStream();

        Result fail = Result.fail(e.getMessage());
        out.write(JSONUtil.toJsonStr(fail).getBytes("UTF-8"));

        out.flush();
        out.close();
    }
}

用户授权

security从数据库中获取用户信息
需要重写 UserDetailsService

/**
 * security从数据库中获取用户信息
 *
 * 需要重写 UserDetailsService
 * @author Tu_Yooo
 * @Date 2021/5/26 10:31
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    

        //数据库中获取用户信息
        SysUser sysUser = sysUserService.getByUsername(username);

        if (sysUser == null)
            throw new UsernameNotFoundException("用户名或密码不正确");

        //参数: 用户id 用户名 用户密码 权限信息
        return new AccountUser(sysUser.getId(),sysUser.getUsername(),sysUser.getPassword(),getUserAuthority(sysUser.getId()));
    }

    /**
     * 获取用户权限信息 <角色 菜单>
     * @param userId 用户id
     * @return 权限信息
     */
    public List<GrantedAuthority> getUserAuthority(Long userId){
    
    

        //角色(ROLE_admin) 菜单操作权限 sys:user:list,sys:user:save
        String authority = sysUserService.getUserAuthority(userId);

        //将字符串通过工具类进行解析
        return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
    }

}

返回结果UserDetails默认实现类是User,为了方便后续,新增其他字段我们重写它,使用自己的UserDetails

/**
 *
 * 封装用户信息
 *
 * UserDetails 默认实现类 User
 * 我们需要去重写它 方便日后字段扩展
 * @author Tu_Yooo
 * @Date 2021/5/26 10:45
 */
public class AccountUser implements UserDetails {
    
    

    ///扩展字段 用户id
    private Long userId;
    

    private static final long serialVersionUID = 530L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;//密码
    private final String username; //用户名
    private final Collection<? extends GrantedAuthority> 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<? extends GrantedAuthority> 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<? extends GrantedAuthority> authorities) {
    
    
        if (username != null && !"".equals(username) && password != null) {
    
    
            this.userId=userId;
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = authorities;
        } else {
    
    
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> 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;
    }
}

这里我们使用了sysUserService查询了数据库权限信息
它必须是一段逗号分割的字符串并交给Security的工具类进行解析
如:ROLE_admin,sys:user:list,sys:user:save
数据库大致如下,建表语句我们提供在后面:
在这里插入图片描述
查询用户id关联的权限信息和角色,查询完成之后解析成字符串并返回

   /**
     * 根据用户id 获取用户关联的权限信息
     * @param userId 用户id
     * @return 权限信息字符串
     */
    @Override
    public String getUserAuthority(Long userId) {
    
    

        String authority = "";

        //角色 ROLE_admin
        List<SysRole> roles = sysRoleService.list(new QueryWrapper<SysRole>()
                .inSql("id", "select role_id from sys_user_role where user_id =" + userId));

        System.out.println(roles);

        if (roles.size() > 0){
    
    
            authority=roles.stream().map(r -> "ROLE_"+r.getCode()).collect(Collectors.joining(",")).concat(",");
        }

        //获取菜单操作权限编码
        List<Long> menuIds = sysUserMapper.getNavMenuIds(userId);
        if (menuIds.size() > 0){
    
    
            List<SysMenu> menus = sysMenuService.listByIds(menuIds);
            String menuPerms = menus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));
            authority = authority.concat(menuPerms);
        }

        return authority;
    }

Security集成

所有的准备工作完成以后,我们需要编写Security配置类,集成所有的过滤器组件

/**
 * Security
 * @author Tu_Yooo
 * @Date 2021/5/25 12:49
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启注解标注 哪些方法需要鉴权 @PreAuthorize("hasRole('admin')") @PreAuthorize("hasAuthority('sys:user:save')")
public class Securityconfig extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private CaptchaFilter captchaFilter;

    //JWT异常处理
    @Autowired
    private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    // 其他异常处理
    @Autowired
    private JWTAccessDeniedHandler jwtAccessDeniedHandler;

    //自定义JWT识别
    @Bean
    JWTAuthenticationFilter jwtAuthenticationFilter() throws Exception {
    
    
        return new JWTAuthenticationFilter(authenticationManager());
    }


    //数据库加密方式
    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder(){
    
    
        return new BCryptPasswordEncoder();
    }
    //数据库获取用户信息
    @Autowired
    private UserDetailsServiceImpl userDetailsService;


    //登出
    @Autowired
    private JWTLogoutSuccessHandler jwtLogoutSuccessHandler;

    //不需要拦截的白名单
    private static final String[] URL_WHITELIST={
    
    
            "/login",
            "/logout",
            "/captcha",
            "/favicon.ico",
            "/swagger-ui.html"
    };

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        //允许跨域 关闭csrf
        http.cors().and().csrf().disable()
        //登录配置
        .formLogin()
                .successHandler(loginFailureHandler) //自定义登录成功时处理
                .failureHandler(loginFailureHandler) //自定义登录失败时处理
         // 登出配置
        .and()
                .logout()
                .logoutSuccessHandler(jwtLogoutSuccessHandler) //登出成功时处理
        //禁用Session
        .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //不生成session
        //配置拦截规则
        .and()
                .authorizeRequests()
                .antMatchers(URL_WHITELIST).permitAll() //白名单中路径 不需要拦截
                .anyRequest().authenticated()
        //异常处理器
        .and()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint) //JWT异常处理
                .accessDeniedHandler(jwtAccessDeniedHandler)//其他异常处理
        //配置自定义过滤器
        .and()
                //自定义JWT识别过滤器
                .addFilter(jwtAuthenticationFilter())
                //自定义验证码过滤器 在 账户密码过滤器之前执行
                .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
    }


    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //数据库认证方式
        auth.userDetailsService(userDetailsService);
    }

}

解决跨域问题

/**
 * MVC扩展配置
 * @author Tu_Yooo
 * @Date 2021/5/25 12:35
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    

    /**
     * 解决跨域问题
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
    
    
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }

    private CorsConfiguration buildConfig() {
    
    
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addExposedHeader("Authorization");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
    
    
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }

}

权限注解说明

当我们集成了Security后,就可以使用它的内置注解来标注controller接口,完成权限控制

Security内置的权限注解:

  • @PreAuthorize:方法执行前进行权限检查
  • @PostAuthorize:方法执行后进行权限检查
  • @Secured:类似于 @PreAuthorize可以在Controller的方法前添加这些注解表示接口需要什么权限。

比如需要Admin角色权限:

@PreAuthorize("hasRole('admin')")

比如需要添加管理员的操作权限

@PreAuthorize("hasAuthority('sys:user:save')")

ok,我们再来整体梳理一下授权、验证权限的流程:

  • 用户登录或者调用接口时候识别到用户,并获取到用户的权限信息注解标识
  • Controller中的方法需要的权限或角色
  • Security通过FilterSecurityInterceptor匹配URI和权限是否匹配有权限则可以访问接口,当无权限的时候返回异常交给AccessDeniedHandler操作类处理
    @Autowired
    private SysUserService sysUserService;

    @PreAuthorize("hasRole('admin')")
    @GetMapping("/test")
    public Result test(){
    
    
        return Result.success(sysUserService.list());
    }

致谢

本文参考B站UP主MarkerHub视频笔记整理视频链接
参考文档:MP使用及建表语句都在其中哟~

猜你喜欢

转载自blog.csdn.net/weixin_46684099/article/details/117434577