vue2+springsecurity权限系统的实现

来自:网络
时间:2023-05-17
阅读:
目录

准备内容

新建项目,SpringBoot采用2.7.11,spring-boot-starter-security,vue 2.0

默认会自动启动拦截装置,可以这样取消,先这样配置一下

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})

JWT 校验用户的凭证(头部,载荷,签证)

引入依赖

<!-- JWT -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>

逻辑:用户登录后,请求头中加一个token参数,如果有则表示可以访问,没有就不能访问,这是一个小测试还是有点缺陷的

SpringSecurity 权限控制

一共有两个最主要拦截器

  • 登陆验证拦截器AuthenticationProcessingFilter

  • 资源管理拦截器AbstractSecurityInterceptor

但拦截器里面的实现需要一些组件来实现分别是

  • AuthenticationManager认证管理器

  • accessDecisionManager决策管理器等组件来支撑。

流程图解读:

  • 用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实 现类。

  • 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证 。

  • 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权 限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。

  • SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication
    通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出 AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实 现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道 web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着 一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至 Authentication。

现在关掉之前的SecurityAutoConfiguration.class,打开是不是直接跳转到了登录页面呢,无法访问接口了

配置 SpringSecurity

先说下创建token的工具类

/**
 * 签发JWT
 *
 * @param id 用户ID
 * @param subject   可以是JSON数据 尽可能少
 * @param ttlMillis 不知道
 * @return 结果
 */
public static String createJWT(String id, String subject, long ttlMillis) {
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    // 获取当前时间毫秒
    long nowMillis = System.currentTimeMillis();
    // 获取当前时间日期类型
    Date now = new Date(nowMillis);
    // 生成一个加密的key
    SecretKey secretKey = generalKey();
    JwtBuilder builder = Jwts.builder()
            .setId(id)
            .setSubject(subject)   // 主题
            .setIssuer("ChiHaiKeJi2016")     // 签发者
            .setIssuedAt(now)   // 签发时间
            .signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
    if (ttlMillis >= 0) {
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        builder.setExpiration(expDate); // 过期时间
    }
    return builder.compact();
}
/**
 * 生成jwt token
 * @param username 用户名
 */
public static String genJwtToken(String username) {
    return createJWT(username, username, 60 * 60 * 1000);
}
/**
 * 生成加密Key
 */
public static SecretKey generalKey() {
    // 目前只知道这个方法生成一个key,并用base64解码
    byte[] encodedKey = Base64.decode(JwtConstant.JWT_SECERT);
    // 目前不知道啥意思
    return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}

配置信息--新建SecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    // 白名单
    private static final String[] URL_WHITE_LIST = {"/login"};
    @Resource
    private LoginHandler loginHandler;
    // 密码加密方式
    @Bean
    protected PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 开启跨域 以及csrf攻击关闭
        httpSecurity.cors().and().csrf().disable()
                // 登录登出配置
                .formLogin()
                // 登录成功接口
                .successHandler(loginHandler)
                // 登录失败的接口
                .failureHandler(loginHandler)
//                .and().logout().logoutSuccessHandler()
                // session禁用配置
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态,前后端分离
                // 拦截规则配置 // // 白名单内容放行
                .and().authorizeRequests().antMatchers(URL_WHITE_LIST).permitAll()
                // 需要认证
                .anyRequest().authenticated();
        return httpSecurity.build();
    }
}

配置登录失败与成功接口---新建---LoginHandler 处理类

/**
 * 登录成功与失败处理器
 */
@Component
public class LoginHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
    // 登录成功的
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        ServletOutputStream stream = response.getOutputStream();
        // 获取token
        String token = JwtUtils.genJwtToken("user");
        // 写入数据
        stream.write(JSONUtil.toJsonStr(new Result("登录成功", token)).getBytes());
        stream.flush();
        stream.close();
    }
    // 登录失败的
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        ServletOutputStream stream = response.getOutputStream();
        String message = exception.getMessage();
        if (exception instanceof BadCredentialsException) {
            message = "用户名或者密码错误!";
        }
        stream.write(JSONUtil.toJsonStr(new Result(message)).getBytes(StandardCharsets.UTF_8));
        stream.flush();
        stream.close();
    }
}

在用户service实现类中自定义查询数据库

@Service
public class SysUserServiceImpl implements SysUserService, UserDetailsService {
    @Resource
    private SysUserMapper sysUserMapper;
    // 自定义查询参数是用户名
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<SysUser> sysUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
        sysUserLambdaQueryWrapper.eq(SysUser::getUsername, username);
        SysUser user = sysUserMapper.selectOne(sysUserLambdaQueryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException("用户名或密码错误!");
        } else if ("1".equals(user.getStatus())) {
            throw new UsernameNotFoundException("该用户已被封禁!");
        }
        // 第三个是拥有权限
        return new User(user.getUsername(), user.getPassword(), getUserAuthority());
    }
    // 权限
    private List<GrantedAuthority> getUserAuthority() {
        return new ArrayList<>();
    }
}

再来说下前端,先配置跨域

module.exports = {
  devServer: {
    host: '0.0.0.0', // 可以忽略不写
    port: 8080, // 它是用来修改你打开后的端口号的
    open: true, // 值为 true的话,项目启动时自动打开到浏览器里边, false不会打开
    proxy: {
      '/api': {
        target: 'http://localhost:82', // 跨域请求的公共地址
        ws: false, // 也可以忽略不写,不写不会影响跨域
        changeOrigin: true, // 是否开启跨域,值为 true 就是开启, false 不开启
        pathRewrite: {
          '^/api': ''// 注册全局路径, 但是在你请求的时候前面需要加上 /api
        }
      }
    }
  }
}

配置axios,因为配置了跨域,这个时候可以直接写api了

import axios from 'axios'
const request = axios.create({
  baseURL: '/api' // 因为配置跨域了
})
// 下面这是拦截器,拦截请求,自动加token令牌
request.interceptors.request.use(
  config => {
    config.headers.token = `${localStorage.getItem('token')}`
    return config
  }
)
export default request

说下如何请求前端登录接口POST请求
    登录接口就是login,传入username和password就可以了

export const Login = function (data) {
  return request({
    method: 'post',
    url: 'login',
    data: qs.stringify(data)
  })
}
返回顶部
顶部