项目集成SpringSecurity框架

news2024/11/18 15:44:58

目录

项目没集成SpringSecurity框架的实现

项目之前的登录接口

 LoginReqVo 接收前端的数据类型

LoginRespVo返回给前端的数据

项目集成SpringSecurity

第一步:导入依赖

第二步:创建security包结构

第三步:实现认证过滤器

第一步:自定义认证过滤器继承AbstractAuthenticationProcessingFilter 

第二步:自定义类实现UserDetailsService接口

第三步:自定义类实现UserDetail接口,自定义设置一个用户详情对象

第四步:编写认证成功后回调的方法和认证失败后回调的方法

第五步:在配置类定义这个自定义过滤器,并定义登录接口(/api/login)

第六步:测试

第四步:实现授权过滤器

 第一步:自定义类继承OncePerRequestFilter

第二步:在配置类中定义这个自定义授权过滤器

第三步:测试

第五步:实现拒绝处理器

第一步:自定义类实现AccessDeniedHandler接口

 第二步:自定义类实现 AuthenticationEntryPoint接口

第三步:在配置类中定义这两个处理器

第四步:测试

第六步:给接口配置权限信息标识


我们需要在项目中集成SpringSecurity框架,主要的步骤就是在登录路径(/api/login)设置认证过滤器

项目没集成SpringSecurity框架的实现

项目之前的登录接口

   @ApiImplicitParams({
            @ApiImplicitParam(paramType = "body", dataType = "LoginReqVo", name = "loginReqVo", value = "用户的用户名,密码,验证码", required = true)
    })
    @ApiOperation(value = "登录功能", notes = "用户登录功能", httpMethod = "POST")
    @PostMapping("/login")
    public R<LoginRespVo> login(@RequestBody LoginReqVo loginReqVo){//接收前端发送的json数据并封装到LoginReqVo类对象
        return userService.login(loginReqVo);
    }

 LoginReqVo 接收前端的数据类型

/**
 * 接收登录功能发过来的json数据,变量名字必须与json数据的key值一样
 */
@Data
@ApiModel(description = "用户登录要的信息")
public class LoginReqVo {
    @ApiModelProperty("用户名字")
    private String username;
    @ApiModelProperty("用户明文密码")
    private String password;
    //验证码
    @ApiModelProperty("验证码")
    private String code;
    //sessionId
    @ApiModelProperty("sessionId")
    private String sessionId;

}

LoginRespVo返回给前端的数据

/**
 * 通过登录功能返回给前端的数据类型
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ApiModel(description = "用户的基本信息")
public class LoginRespVo {
    /**
     * 用户id
     * TODO:由于Long类型的数字长度过长时,发送给前端的时候会数据丢失,所以需要把Long类型的id变成String类型
     * TODO:再发送给前端
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty(value = "用户id")
    private Long id;
    //用户名字
    @ApiModelProperty(value = "用户名字")
    private String username;
    //昵称
    @ApiModelProperty(value = "用户昵称")
    private String nickName;
    //电话
    @ApiModelProperty(value = "用户电话")
    private String phone;
    @ApiModelProperty("真实名称")
    private String realName;
    @ApiModelProperty("性别")
    private Integer sex;
    @ApiModelProperty("状态")
    private Integer status;
    @ApiModelProperty("邮件")
    private String email;
    @ApiModelProperty("侧边栏权限树(不包含按钮权限)")
    private List<MenuDomain>menus;
    @ApiModelProperty("按钮权限标识")
    private List<String>permissions;
    /**
     * 用户登录成功后,将用户的id和name信息经过base64编码,作为票据token响应给前端
     * accessToken会保存在浏览器前端,同时前端访问后端接口时,会在请求头中携带票据信息,其中请求头中的key为Authorization,value为accessToken对应的值;
     * 后端只需从请求头中获取Authorization对应的值,即可知道当前来自哪个用户的访问了
     */
    @ApiModelProperty("票据token")
    private String accessToken;
}

项目集成SpringSecurity

第一步:导入依赖

这里顺便把jjwt依赖也导入,方便以后生成jwt票据

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
<!--        导入springSecurity依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

第二步:创建security包结构

第三步:实现认证过滤器

第一步:自定义认证过滤器继承AbstractAuthenticationProcessingFilter 

在attemptAuthentication尝试认证方法中

1.先判断请求方法是不是post方式,和请求的数据是不是json数据,如果不是就抛出异常

2.解析前端传入的json数据成 LoginReqVo接收前端数据的数据类型

  //获取reqeust请求对象的发送过来的数据流
        ServletInputStream in = request.getInputStream();
        //将数据流中的数据反序列化成获取前端数据的请求封装类
        LoginReqVo loginReqVo = new ObjectMapper().readValue(in, LoginReqVo.class);

3.然后根据sessionId去Redis查看验证码是否正确,如果正确就把用户的用户名和用户的明文密码封装到用户票据类型对象( UsernamePasswordAuthenticationToken)中,然后调用认证管理器进行认证 

/**
 * 自定义过滤器,继承AbstractAuthenticationProcessingFilter类
 * 核心作用是 认证用户信息,如果认证成功就颁发票据
 */
public class JwtLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private RedisTemplate redisTemplate;
    private static final String USER_NAME="username";
    private static final String PASSWORD="password";
    /**
     * 自定义构造器,传入认证登录的url地址
     * @param loginUrl 登录的url地址
     */
    public JwtLoginAuthenticationFilter(String loginUrl, RedisTemplate redisTemplate) {
        super(loginUrl);
        this.redisTemplate=redisTemplate;
    }

    /**
     * 尝试去认证的方法,把前端返回的用户名和密码封装到UsernamePasswordAuthenticationToken认证凭对象;
     * data:{"username":"hhh","password":"123456"}
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //判断请求方法必须是post提交,且提交的数据的内容必须是application/json格式的数据
        if (!request.getMethod().equalsIgnoreCase("POST") || !MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()))
        {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //获取post请求ajax前端传入的json数据,并解析成map集合
        //获取请求参数
        //获取reqeust请求对象的发送过来的数据流
        ServletInputStream in = request.getInputStream();
        //将数据流中的数据反序列化成获取前端数据的请求封装类
        LoginReqVo loginReqVo = new ObjectMapper().readValue(in, LoginReqVo.class);

        //设置返回的格式
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //判断参数是否合法
        if(loginReqVo==null|| StringUtils.isBlank(loginReqVo.getUsername())||StringUtils.isBlank(loginReqVo.getPassword())){
            R<Object> error = R.error(ResponseCode.DATA_ERROR);
            //将对象序列化成json数据
            String jsonData = new ObjectMapper().writeValueAsString(error);
            response.getWriter().write(jsonData);
            //直接结束此方法,不执行下面的步骤
            return null;
        }
        //判断验证码是否存在
        if(StringUtils.isBlank(loginReqVo.getCode())){
            R<Object> error = R.error(ResponseCode.CHECK_CODE_NOT_EMPTY);
            //将对象序列化成json数据
            String jsonData = new ObjectMapper().writeValueAsString(error);
            response.getWriter().write(jsonData);
            //直接结束此方法,不执行下面的步骤
            return null;
        }
        //判断验证码是否正确,根据sessionId(key)去redis查找验证码(value),TODO:别忘了验证码前缀
        String code = (String) redisTemplate.opsForValue().get(StockConstant.CHECK_PREFIX+loginReqVo.getSessionId());
        if(StringUtils.isBlank(code)){//取出来的验证码为空
            R<Object> error = R.error(ResponseCode.CHECK_CODE_TIMEOUT);
            //将对象序列化成json数据
            String jsonData = new ObjectMapper().writeValueAsString(error);
            response.getWriter().write(jsonData);
            //直接结束此方法,不执行下面的步骤
            return null;
        }
        if(!code.equalsIgnoreCase(loginReqVo.getCode())){//忽略大小写比较
            R<Object> error = R.error(ResponseCode.CHECK_CODE_ERROR);
            //将对象序列化成json数据
            String jsonData = new ObjectMapper().writeValueAsString(error);
            response.getWriter().write(jsonData);
            //直接结束此方法,不执行下面的步骤
            return null;
        }

        String username = loginReqVo.getUsername();
        username = (username != null) ? username : "";
        username = username.trim();
        String password = loginReqVo.getPassword();
        password = (password != null) ? password : "";

        //将用户名和密码信息封装到认证票据对象下
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        //调用认证管理器认证指定的票据对象
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 认证成功后执行的方法
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    }

    /**
     * 认证失败执行的方法
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {

    }
}

第二步:自定义类实现UserDetailsService接口

认证管理器会调用UserDetailsService的loadUserByUsername的方法

然后在这个方法的方法参数中,会从用户票据对象的username,获取数据值

1.然后通过这个用户名去数据查找这个用户的所有详细信息

2.封装要给前端返回的权限菜单,和权限按钮

//获取用户的所有权限信息
        List<SysPermission> perms = sysPermissionMapper.getAllPermission(dbUser.getUsername());

        //通过工具类获取权限实体类集合的权限菜单,不包括权限按钮,0作为pid(父亲id)
        List<MenuDomain>menus = ParsePermission.recursionGetMenus(perms, 0L);
        //通过工具类获取权限实体类集合的权限按钮(type==3)
        List<String> permissions = ParsePermission.getButtonCode(perms);

工具类

public class ParsePermission {
    /**
     * 获取权限实体类集合中的所有权限按钮
     * @param permissions 权限实体类集合
     */
    public static List<String>getButtonCode(List<SysPermission>permissions){
        return permissions.stream().filter(permission->
                !Strings.isNullOrEmpty(permission.getCode())&&permission.getType()==3//只有type==3才是button权限按钮
        ).map(SysPermission::getCode)//获取SysPermission类中的code属性,并收集起来
                .collect(Collectors.toList());
    }

    /**
     * 获取侧边栏权限树(不包含按钮权限)
     * @param permissions 权限实体对象集合
     * @param pid 父亲id
     */
    public static List<MenuDomain> recursionGetMenus(List<SysPermission>permissions,Long pid){
        return permissions.stream()
                .filter(permission -> permission.getPid().equals(pid))//父id相同
                .filter(permission->permission.getType()!=3)//类型不是3,即不是按按钮
                .map(permission -> {//组装MenuDomain菜单类型
                    return MenuDomain.builder()
                            .id(permission.getId())
                            .title(permission.getTitle())
                            .icon(permission.getIcon())
                            .path(permission.getUrl())
                            .name(permission.getName())
                            //递归调用此方法,获取以这个权限id作为父亲id的所有子权限
                            .children(recursionGetMenus(permissions, permission.getId()))
                            .build();
                }).collect(Collectors.toList());
    }
}

3.封装springSecurity需要的权限表示,包括ROLE_角色名称 和 权限自身

 //获取SpringSecurity的权限表示:ROLE_角色名称 和 权限自身
        ArrayList<String>authorities=new ArrayList<>();
        List<String> ps = perms.stream()
                .filter(p -> StringUtils.isNotBlank(p.getPerms()))
                .map(SysPermission::getPerms).collect(Collectors.toList());

        List<SysRole>roles=sysRoleMapper.findRoleByUserId(dbUser.getId());
        List<String> rs = roles.stream().map(r -> "ROLE_" + r.getName()).collect(Collectors.toList());

        authorities.addAll(ps);
        authorities.addAll(rs);

4. 将该用户拥有的权限集合转换成权限对象

//将该用户拥有的权限集合转换成权限对象
        //先把集合变成String数组
        String[] psArray = authorities.toArray(new String[authorities.size()]);
        List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(psArray); 

5. 构建用户详情对象,之后认证管理器会使用这个详情对象的密文密码与用户票据对象的明文密码进行比对


        LoginUserDetail loginUserDetail = new LoginUserDetail();
        BeanUtils.copyProperties(dbUser,loginUserDetail);
        loginUserDetail.setAuthorities(authorityList);
        loginUserDetail.setMenus(menus);
        loginUserDetail.setPermissions(permissions);

 

/**
 * 定义从数据库获取用户详情服务bean
 */
@Service("userDetailsService")
public class LoginUserDetailService implements UserDetailsService {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysPermissionMapper sysPermissionMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    /**
     * 根据用户票据类对象提供的用户名去数据库查找用户的详细信息(用户名,用户密文密码,用户的权限集合)
     * @param username 用户票据类对象提供的用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.根据用户名去数据库查找用户的详细信息
        SysUser dbUser = sysUserMapper.findUserInfoByUserName(username);
        if(dbUser==null){
            throw new UsernameNotFoundException("该用户不存在");
        }
        //2.组装UserDetails对象
        //springSecurity默认的UserDetails实现类User只有用户名,用户密码密码,用户的权限集合
        //但是要返回给前端的信息还包括id email,menus等,所以我们要自定义实现类


        //获取用户的所有权限信息
        List<SysPermission> perms = sysPermissionMapper.getAllPermission(dbUser.getUsername());

        //通过工具类获取权限实体类集合的权限菜单,不包括权限按钮,0作为pid(父亲id)
        List<MenuDomain>menus = ParsePermission.recursionGetMenus(perms, 0L);
        //通过工具类获取权限实体类集合的权限按钮(type==3)
        List<String> permissions = ParsePermission.getButtonCode(perms);

        //获取SpringSecurity的权限表示:ROLE_角色名称 和 权限自身
        ArrayList<String>authorities=new ArrayList<>();
        List<String> ps = perms.stream()
                .filter(p -> StringUtils.isNotBlank(p.getPerms()))
                .map(SysPermission::getPerms).collect(Collectors.toList());

        List<SysRole>roles=sysRoleMapper.findRoleByUserId(dbUser.getId());
        List<String> rs = roles.stream().map(r -> "ROLE_" + r.getName()).collect(Collectors.toList());

        authorities.addAll(ps);
        authorities.addAll(rs);

        //将该用户拥有的权限集合转换成权限对象
        //先把集合变成String数组
        String[] psArray = authorities.toArray(new String[authorities.size()]);
        List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(psArray);

        //构建用户详情对象,之后会使用这个详情对象的密文密码与用户票据对象的明文密码进行比对
        LoginUserDetail loginUserDetail = new LoginUserDetail();
        BeanUtils.copyProperties(dbUser,loginUserDetail);
        loginUserDetail.setAuthorities(authorityList);
        loginUserDetail.setMenus(menus);
        loginUserDetail.setPermissions(permissions);

        return loginUserDetail;
    }
}

第三步:自定义类实现UserDetail接口,自定义设置一个用户详情对象

认证管理器在调用完 loadUserByUsername方法获取用户详细信息并封装到用户详情对象后,认证管理器会自动使用BrcryptPassword(需要我们定义这个bean)对用户票据对象的明文密码和用户详情对象的密文密码进行比对

比对成功后会回调认证过滤器的成功认证的方法,我们只需要在这个方法中返回前端所需要的json数据即可,但是默认的User(默认实现UserDetail接口的用户详情对象)只有用户名,用户密文密码,用户权限集合三个成员变量,无法满足给前端返回的数据,所以我们需要自定义一个类实现UserDetail接口

/**
 * 自定义用户详情对象类
 */
@Data
public class LoginUserDetail implements UserDetails {
    /**
     * 用户名
     */
    private String username;
    /**
     * 用户密文密码
     */
    private String password;
    /**
     * 用户的权限集合
     */
    private List<GrantedAuthority>authorities;
    //@JsonSerialize(using = ToStringSerializer.class)
    /**
     * 用户id
     */
    private Long id;

    /**
     * 用户昵称
     */
    private String nickName;
    /**
     * 用户电话
     */
    private String phone;
    /**
     * 真实名称
     */
    private String realName;
    /**
     * 性别
     */
    private Integer sex;
    /**
     * 状态
     */
    private Integer status;
    /**
     * 邮件
     */
    private String email;
    /**
     * 侧边栏权限树(不包含按钮权限)
     */
    private List<MenuDomain>menus;
    /**
     * 按钮权限标识
     */
    private List<String>permissions;


 /*   @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }*/

/*    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return null;
    }*/
    /**
     * true:账号没有过期
     */
    private boolean isAccountNonExpired=true;
    /*@Override
    public boolean isAccountNonExpired() {
        return false;
    }*/
    /**
     * true:账户没有被锁定
     */
    private boolean isAccountNonLocked=true;
   /* @Override
    public boolean isAccountNonLocked() {
        return false;
    }*/

    /**
     * true:密码没有过期
     */
    private boolean isCredentialsNonExpired=true;
   /* @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }*/

    /**
     * true:账户可用
     */
    private boolean isEnabled=true;
   /* @Override
    public boolean isEnabled() {
        return false;
    }*/
}

第四步:编写认证成功后回调的方法和认证失败后回调的方法

在successfulAuthentication()成功认证的回调方法中

获取用户详情对象的权限集合对象,使用工具类把用户名和权限封装成token票据

工具类

public class JwtTokenUtil {
    // Token请求头
    public static final String TOKEN_HEADER = "authorization";
    // Token前缀
    public static final String TOKEN_PREFIX = "Bearer ";

    // 签名主题
    public static final String SUBJECT = "JRZS";
    // 过期时间,单位毫秒
    public static final long EXPIRITION = 1000 * 60 * 60* 24 * 7;
    // 应用密钥
    public static final String APPSECRET_KEY = "hhha";
    // 角色权限声明
    private static final String ROLE_CLAIMS = "role";

    /**
     * 生成Token
     */
    public static String createToken(String username,String role) {
        Map<String,Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);

        String token = Jwts
                .builder()
                .setSubject(username)
                .setClaims(map)
                .claim("username",username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
        return token;
    }

    /**
     * 校验Token
     */
    public static Claims checkJWT(String token) {
        try {
            final Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
            return claims;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从Token中获取用户名
     */
    public static String getUsername(String token){
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    /**
     * 从Token中获取用户角色
     */
    public static String getUserRole(String token){
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("role").toString();
    }

    /**
     * 校验Token是否过期
     */
    public static boolean isExpiration(String token){
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }
}

/**
     * 认证成功后执行的方法
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
       //获取封装后的用户对象
        //这一个principal对象里的密码为null
       LoginUserDetail principal=(LoginUserDetail)authResult.getPrincipal();
        String username = principal.getUsername();
        Collection<GrantedAuthority> authorities = principal.getAuthorities();

        //使用工具类生成jwt令牌,authorities.toString()把权限集合变成["P1","ROLE_ADMIN]类型
        //构建JwtToken 加入权限信息是为了将来访问时,jwt解析获取当前用户对应的权限,做授权的过滤
        String token = JwtTokenUtil.createToken(username, authorities.toString());

        //设置返回数据格式
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //设置返回数据编码格式
        response.setCharacterEncoding("UTF-8");

        //构建响应实体对象
        LoginRespVo loginRespVo = new LoginRespVo();
        BeanUtils.copyProperties(principal,loginRespVo);
        loginRespVo.setAccessToken(token);//设置票据

        R<LoginRespVo> info = R.ok(loginRespVo);


        //把类对象变成json数据
        String jsonData = new ObjectMapper().writeValueAsString(info);
        //将json数据返回
        response.getWriter().write(jsonData);
    }

    /**
     * 认证失败执行的方法
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        //设置响应数据为json
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //设置响应数据格式
        response.setCharacterEncoding("UTF-8");
        //设置响应的数据
        R<Object> info = R.error(ResponseCode.ERROR);
        //将数据封装成json数据
        String jsonData = new ObjectMapper().writeValueAsString(info);
        response.getWriter().write(jsonData);
    }

第五步:在配置类定义这个自定义过滤器,并定义登录接口(/api/login)

@Configuration
@EnableWebSecurity//开启web安全设置生效
//开启SpringSecurity相关注解支持
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 定义公共的无需被拦截的资源
     */
    private String[] getPubPath(){
        //公共访问资源
        String[] urls = {
                "/**/*.css","/**/*.js","/favicon.ico","/doc.html",
                "/druid/**","/webjars/**","/v2/api-docs","/api/captcha",
                "/swagger/**","/swagger-resources/**","/swagger-ui.html"
        };
        return urls;
    }

    //TODO:返回一个BCryptPasswordEncoder密码加密类类,让SpringSecurity自动调用把密码密文和明文进行匹配
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义认证过滤器
     *  隐含:如果认证成功,则在安全上下文中维护认证相关信息
     *      如果安全上下文中存在认证相关信息,则默认的UserNamePasswordAuthenticationFilter认证过滤器就不会执行
     *      所以执行顺序,自定义的过滤器在前,默认的过滤器在后
     * @return
     * @throws Exception
     */
   @Bean
   public JwtLoginAuthenticationFilter jwtLoginAuthenticationFilter() throws Exception {
       JwtLoginAuthenticationFilter jwtLoginAuthenticationFilter = new JwtLoginAuthenticationFilter("/api/login", redisTemplate);
       //设置认证管理器
       jwtLoginAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
       return jwtLoginAuthenticationFilter;
   }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //登出功能,指定登录的url地址
        http.logout().logoutUrl("/api/logout").invalidateHttpSession(true);
        //开启允许iframe 嵌套。security默认禁用ifram跨域与缓存
        http.headers().frameOptions().disable().cacheControl().disable();
        //session禁用
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.csrf().disable();//禁用跨站请求伪造
        http.authorizeRequests()//对资源进行认证处理
                .antMatchers(getPubPath()).permitAll()//公共资源都允许访问
                .anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问



        //对于自定义过滤器,需要创建一个实例,所以用方法返回一个自定义认证过滤器
        http.addFilterBefore(jwtLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

}

第六步:测试

访问/api/login接口,获取票据

第四步:实现授权过滤器

 第一步:自定义类继承OncePerRequestFilter

如果token不为null且token解析正确

就会解析出用户名和用户权限集合,封装到用户票据对象( UsernamePasswordAuthenticationToken)中,然后把这个对象放到安全上下文中,后续的过滤器,比如:认证过滤器如果发现安全上下文中存在token票据对象,就不会进行重新认证

/**
 * 定义授权过滤器,本质就是获取一切请求头的token信息,进行校验
 */
public class JwtAuthorizationFilter extends OncePerRequestFilter {
    /**
     * 过滤中执行的方法
     * @param request
     * @param response
     * @param filterChain 过滤器链
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //1.从请求头中获取token字符串
        String tokenStr = request.getHeader(JwtTokenUtil.TOKEN_HEADER);

        //2.合法性判断
        //2.1 判断票据信息是否为空
        if(StringUtils.isBlank(tokenStr)){
            //如果票据为空,则放行,但是此时安全上下文中没有认证成功的票据,后续的过滤器如果得不到票据,后面的认证过滤器会进行拦截
            filterChain.doFilter(request,response);
            return;
        }
        //2.2检查票据是否合法,解析失败
        Claims claims = JwtTokenUtil.checkJWT(tokenStr);
        if(claims==null){
            //票据不合法,直接让过滤器终止,不放行即可
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            R<Object> error = R.error(ResponseCode.INVALID_TOKEN);
            //把数据序列化成json格式
            String jsonData = new ObjectMapper().writeValueAsString(error);
            response.getWriter().write(jsonData);
            return;
        }
        //2.3
        //获取用户名
        String username = JwtTokenUtil.getUsername(tokenStr);
        //获取用户权限集合 "["P1","ROLE_ADMIN"]"
        String roles = JwtTokenUtil.getUserRole(tokenStr);
        //解析用户权限字符串
        String stripStr = StringUtils.strip(roles, "[]");
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(stripStr);

        //3.组装数据到UsernamePasswordAuthenticationToken票据对象中
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,null,authorities);

        //4.将封装的认证票据存入security安全上下文中,这样后续的过滤器就可以直接从安全上下文中获取用户的相关权限信息
        //TODO:以线程为维度:当前访问结束,那么线程回收,安全上下文中的票据也会回收,下次访问时需要重新解析
        SecurityContextHolder.getContext().setAuthentication(token);

        //5.放行请求,后续的过滤器,比如:认证过滤器如果发现安全上下文中存在token票据对象,就不会进行重新认证
        filterChain.doFilter(request,response);

    }
}

第二步:在配置类中定义这个自定义授权过滤器

 /**
     * 维护一个授权过滤器bean,检查jwt票据是否有效,并做相关处理
     */
    @Bean
    public JwtAuthorizationFilter jwtAuthorizationFilter(){
        return new JwtAuthorizationFilter();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //登出功能,指定登录的url地址
        http.logout().logoutUrl("/api/logout").invalidateHttpSession(true);
        //开启允许iframe 嵌套。security默认禁用ifram跨域与缓存
        http.headers().frameOptions().disable().cacheControl().disable();
        //session禁用
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.csrf().disable();//禁用跨站请求伪造
        http.authorizeRequests()//对资源进行认证处理
                .antMatchers(getPubPath()).permitAll()//公共资源都允许访问
                .anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问



        //对于自定义过滤器,需要创建一个实例,所以用方法返回一个自定义认证过滤器
        http.addFilterBefore(jwtLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //配置授权过滤器,它是资源安全的屏障,优先级最高,所以需要在自定义的认证过滤器之前
        http.addFilterBefore(jwtAuthorizationFilter(), JwtLoginAuthenticationFilter.class);
       

    }

第三步:测试

在请求头中加入 key为:authorization  value为:刚才访问/api/login登录接口返回的票据信息

第五步:实现拒绝处理器

第一步:自定义类实现AccessDeniedHandler接口

这个类就是用户没有权限访问接口时执行处理器

/**
 * 自定义用户没有权限访问一个资源接口时要执行的处理器
 */
@Slf4j
public class StockAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        log.warn("当前用户无法访问资源,原因:{}",e.getMessage());

        //向前端返回用户没有权限的信息
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");

        R<Object> error = R.error(ResponseCode.NOT_PERMISSION);
        //将数据序列化成json数据
        String jsonData = new ObjectMapper().writeValueAsString(error);
        //向前端响应此json数据
        response.getWriter().write(jsonData);
    }
}

 第二步:自定义类实现 AuthenticationEntryPoint接口

这个类就是(没有进行认证登录)匿名用户访问资源接口时执行的处理器

/**
 * 自定义一个匿名用户(此用户没有进行认证)拒绝的处理器
 */
@Slf4j
public class StockAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        log.warn("匿名用户无法访问该资源,原因:{}",e.getMessage());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        R<Object> error = R.error(ResponseCode.ANONMOUSE_NOT_PERMISSION);
        String jsonData = new ObjectMapper().writeValueAsString(error);
        response.getWriter().write(jsonData);
    }
}

第三步:在配置类中定义这两个处理器

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //登出功能,指定登录的url地址
        http.logout().logoutUrl("/api/logout").invalidateHttpSession(true);
        //开启允许iframe 嵌套。security默认禁用ifram跨域与缓存
        http.headers().frameOptions().disable().cacheControl().disable();
        //session禁用
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.csrf().disable();//禁用跨站请求伪造
        http.authorizeRequests()//对资源进行认证处理
                .antMatchers(getPubPath()).permitAll()//公共资源都允许访问
                .anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问



        //对于自定义过滤器,需要创建一个实例,所以用方法返回一个自定义认证过滤器
        http.addFilterBefore(jwtLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //配置授权过滤器,它是资源安全的屏障,优先级最高,所以需要在自定义的认证过滤器之前
        http.addFilterBefore(jwtAuthorizationFilter(), JwtLoginAuthenticationFilter.class);
        //配置用户没有权限时的处理器
        http.exceptionHandling().accessDeniedHandler(new StockAccessDeniedHandler())
                .authenticationEntryPoint(new StockAuthenticationEntryPoint());

    }

第四步:测试

把请求头去掉就是匿名用户

第六步:给接口配置权限信息标识

使用@PreAuthorize注解

注意需要和数据库中的权限一样,即与用户票据对象中的权限集合的值一样

如果不想给后端的接口一一添加权限信息,可以在前端动态获取后端返回的权限集合,直接不在前端显示这个用户不拥有的权限即可

    /**
     * 根据多条件查询用户的基本信息
     * @param vo 多条件的封装类
     */
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "body", dataType = "QueryUserVo", name = "vo", value = "多条件的封装类", required = true)
    })
    @ApiOperation(value = "根据多条件查询用户的基本信息", notes = "根据多条件查询用户的基本信息", httpMethod = "POST")
    @PostMapping("/users")
    @PreAuthorize("hasAuthority('sys:user:list')")
    public R<PageResult<SysUserDomain>>getUserByManyCondition(@RequestBody QueryUserVo vo){
        if(StringUtils.isBlank(vo.getPageNum())){
            vo.setPageNum("1");
        }
        if(StringUtils.isBlank(vo.getPageSize())){
            vo.setPageSize("20");
        }
        return userService.getUserByManyCondition(vo);
    }

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2178527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Modbus调试工具和源码分享

我们应该知道了学习Modbus协议应该具备主从两个设备才行&#xff0c;但是在学习过程中如果没有真实的物理设备&#xff0c;应该怎么调试呢&#xff1f; 我们可以通过软件工具来模拟主从设备&#xff0c;下面我们推荐几个比较实用的工具。 以下内容包含&#xff1a;实用工具、…

超好用的10款视频剪辑软件,从入门到精通

视频剪辑软件哪款比较好呢&#xff1f;无论是专业制作团队、自媒体创作者&#xff0c;还是家庭用户&#xff0c;一款好用的视频剪辑软件都能极大地提升创作效率和作品质量。以下是十款备受推崇的视频剪辑软件&#xff0c;分别从适用人群、易用程度和功能特点进行介绍。 1.影忆…

揭秘移动硬盘RAW:原因、恢复策略与预防措施

移动硬盘RAW概述 移动硬盘&#xff0c;作为现代数据存储的重要工具&#xff0c;其稳定性与数据安全直接关乎用户的数据资产安全。然而&#xff0c;在使用过程中&#xff0c;不少用户会遇到移动硬盘状态突然变为RAW格式的情况&#xff0c;这往往伴随着数据无法直接访问的困扰。…

1688客服代码怎么做生成悬浮客服代码阿里巴巴国内站1688平台悬浮特效悬浮代码悬浮客服 1688客服代码怎么做生成器软件代码工具制作客服代码阿里巴巴

阿里巴巴国内站1688平台悬浮特效悬浮代码悬浮客服 1688客服代码怎么做生成器软件代码工具制作客服代码阿里巴巴 一秒美工工具

王道-操作系统

3 下列说法正确的是_____ 答案:A 解析: A 正确。如链接文件可以顺序存取,但不能随机存取。连续文件可随机存取,也可顺序存取。 B 错误。一个 FCB 就是一个文件目录项。在引入索引节点后,每个文件的目录项只保留文件名和指向该文件对应的索引节点指针,而索引节点的有关信息…

一书直接讲透自然语言处理《Getting Started with Google BERT_ Build and train》

《Getting Started with Google BERT: Build and Train》是一本面向初学者和中级读者的指南&#xff0c;旨在帮助他们理解和使用Google的BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;模型。BERT是近年来自然语言处理&#xff08;NLP&…

WIFI密码默认显示

文章目录 需求分析遇到问题问题原因解决方案 需求 在进入设置&#xff0c;点击某一个wifi,连接wifi 界面&#xff0c;显示密码默认选中状态&#xff0c;效果如下 分析 在 WiFi密码被输入法挡住 中我们已经分析了整个流程&#xff0c;布局文件和控制中心。 结局系统设置WIFI连…

9.29总结

这星期学了概率和组合数学 这是我觉得的一个有趣的题目&#xff0c;每个人身上都有n-1根绳子&#xff0c;如果组不成稳定三角&#xff0c;那么肯定有两个人相邻两根绳子颜色不一样&#xff0c;那么每两个这样的人就会贡献一个不稳定三角形&#xff0c;所以只要所有三角形减去每…

Linux 进程间通信(共享内存+消息队列)

目录 一.共享内存 1.底层原理和系统接口 a.底层原理 b.系统接口 ① shmget ② shmat ③ shmdt ④ shmctl c.命令行控制指令 2.共享内存的通信特点 a.共享内存间的通信没有任何的同步机制 b.共享内存是所有进程间通信速度最快的 c.共享内存可以提供较大的空间。 3…

网络游戏通信方案概述

弱联网和强联网游戏 长连接和短连接游戏 Socket、Http、FTP 总结

亚马逊AI编程工具Amazon Q 和 Amazon CodeWhisperer使用教程

Amazon CodeWhisperer 主要功能&#xff1a;用于代码生成和编程辅助。它可以在 IDE 中提供代码建议、自动补全代码、生成代码片段等。使用场景&#xff1a;主要用于开发者在编写代码时提高效率&#xff0c;减少重复性工作。特点&#xff1a;支持多种编程语言&#xff0c;集成在…

composer环境变量(phpstudy集成环境)无法使用问题

composer 不是内部或外部命令,也不是可运行的程序 或批处理文件。 按下WinR组合键打开“运行”&#xff0c;输入sysdm.cpl 回车&#xff0c;打开“系统属性”并切换至“高级”选项卡&#xff0c;点击“环境变量”进行配置 配置完后点击确定&#xff0c;重新打开命令行&#x…

leetcode:LCR 169. 招式拆解 II(python3解法)

难度&#xff1a;简单 某套连招动作记作仅由小写字母组成的序列 arr&#xff0c;其中 arr[i] 第 i 个招式的名字。请返回第一个只出现一次的招式名称&#xff0c;如不存在请返回空格。 示例 1&#xff1a; 输入&#xff1a;arr "abbccdeff" 输出&#xff1a;a示例 2…

mac Wireshark You do not have permission to capture on device “rvio“.

原因&#xff1a; 权限不足 解决方案&#xff1a; 打开终端在终端输入 whoamin (会在终端显示本机的实际用户名字) 例如&#xff1a;xiaoming进入 /dev 目录 cd /dev输入命令&#xff1a;ls -la | grep bp输入命令&#xff1a;sudo chown whoamin xiaoming:admin bp*重新打开 …

随机掉落的项目足迹:Vue3 + wangEditor5富文本编辑器——toolbar.getConfig() 查看工具栏的默认配置

问题引入 小提示&#xff1a;问题引入是一个讲故事的废话环节&#xff0c;各位小伙伴可以直接跳到第二大点&#xff1a;问题解决 我的项目不需要在富文本编辑器中引入添加代码块的功能&#xff0c;于是我寻思在工具栏上把操作代码的菜单删一删 于是我来到官网文档工具栏配置 …

物联网系统中OLED屏主流驱动方案详解

01 物联网系统中为什么要使用OLED驱动芯片 卓越的显示效果 1、高对比度和鲜艳色彩&#xff1a;OLED屏幕能够自发光&#xff0c;因此能够实现极高的对比度和鲜艳的色彩表现&#xff0c;这在物联网设备的显示界面上尤为重要&#xff0c;可以为用户提供更清晰、更生动的视觉体验…

next 从入门到精通

next 从入门到精通 相关链接 演示地址 演示地址 源码地址 源码地址 获取更多 获取更多 hello 大家好&#xff0c;我是 数擎科技&#xff0c;今天来跟大家聊聊 Next.js 如果你遇到任何问题&#xff0c;欢迎联系我 m-xiaozhicloud 什么是 Next.js Next.js 是一个基于 Reac…

从零开始搭建UVM平台(四)-加入interface

书接上回&#xff1a; 从零开始搭建UVM平台&#xff08;一&#xff09;-只有uvm_driver的验证平台 从零开始搭建UVM平台&#xff08;二&#xff09;-加入factory机制 从零开始搭建UVM平台&#xff08;三&#xff09;-加入objection机制 加入interface 上述代码还有一个问题…

目标检测论文常用评价指标(Evaluation Metrics)总结

评价指标&#xff08;Evaluation Metrics&#xff09; 混淆矩阵&#xff08;Confusion Matrix&#xff09;归一化混淆矩阵&#xff08;Normalized Confusion Matrix&#xff09;精确度&#xff08;Precision&#xff09;召回率&#xff08;Recall&#xff09;F1值&#xff08;F…

连续4年!容联云荣登2024北京民营企业百强榜单

近日&#xff0c;北京市工商联发布2024北京民营企业百强“14”榜单。容联云荣登北京民营企业科技创新前100强&#xff0c;同时位列12家朝阳区科技百强企业之一。 自2018年起&#xff0c;在中共北京市委统战部的指导下&#xff0c;北京市工商业联合会联合有关部门&#xff0c;每…