首页 > 其他分享 >Spring Security认证授权流程

Spring Security认证授权流程

时间:2024-07-24 21:53:46浏览次数:9  
标签:return Spring worker 认证 response new Security public

1.认证授权过程

1.使用用户名和密码进行登录操作。

2.后端接受请求进入层层Filter过滤器,对登录操作放行,放行后开始认证流程。

3.Spring Security拿到用户名和密码,将其封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken。

4.使用认证管理器把上一步产生的TOKEN对象进行登录认证。也就是authenticationManager.authenticate(authenticationToken)

5.认证管理器AuthenticationManager认证

认证成功:返回一个封装了用户角色权限等信息的Authentication对象。

认证失败:抛出AuthenticationException异常,异常将被捕获交给AuthenticationEntryPoint 处理,处理后返回给前端页面引导重新登陆。

6.SecurityContextHolder.getContext().setAuthentication(…) ,把认证管理器返回的Authentication 对象赋予给当前的 SecurityContext上下文中。

7.认证成功后访问受保护的资源时,会使用保存在SecurityContext中的Authentication 对象进行相关的权限鉴定 ,若鉴定失败抛出AccessDeniedException,异常将被ExceptionTranslationFilter处理 ,然后返回状态码403表示没有权限。

2.举个栗子

配置项

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityInDBConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;


    //密码加密器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    //基于数据库认证&授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()  //关闭csrf
            .cors()  //解决跨域问题
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //前后端分离,禁用session
            .and()
             //请求认证配置
            .authorizeRequests()
            .mvcMatchers("/system/login")
            .permitAll()
            .anyRequest().authenticated();

        //认证授权过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter , UsernamePasswordAuthenticationFilter.class);

        //异常处理器
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)  //401异常 认证失败
                .accessDeniedHandler(accessDeniedHandler);  //403异常  授权失败
    }

    //认证管理器
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

        SpringSecurity默认是前后端不分离的方式,因此前后端分离项目需要禁言Session。然后排除登录的请求接口。由于在认证前需要验证Token,因此添加一个Token过滤器来进行Token验证。

        然后还需要指定401和403的失败情况的处理器,401异常进入AuthenticationEntryPoint进行处理,403鉴权失败异常进入AccessDeniedHandler进行处理,因此我们只需手动声明这两个Bean对象到IOC容器中,发生异常时就会自动进入到相应Bean中进行处理。

代码如下:


/**
 * 针对于Security的异常401 403统一做异常处理器
 */
@Configuration
public class SecurityHandler {

    //认证失败
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint(){
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException){
//使用封装好的工具把字符串响应给客户端
                WebUtils.renderString(response , JSONUtil.toJsonStr(Result.error("401 认证失败,请先登陆")),401);
            }
        };
    }

    //授权失败
    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
                WebUtils.renderString(response, JSONUtil.toJsonStr(Result.error("403 授权失败,没有权限访问")),403);
            }
        };
    }
}

响应工具

/**
 * 将字符串渲染到客户端,向响应之中写入中聚类
 */
public class WebUtils {

    /**
     * 将字符串渲染到客户端
     * @param response 渲染对象
     * @param data   待渲染的字符串
     */
    public static String renderString(HttpServletResponse response, String data,Integer code) {
        try {
            response.setStatus(code);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
}

UserDetails



/**
 * @description 存储worker的角色以及多个权限
 */

@NoArgsConstructor
@Data
public class LoginUser implements UserDetails {
    private Worker worker;

    public LoginUser(Worker worker) {
        this.worker = worker;
    }

    //已授予的权限
    private List<SimpleGrantedAuthority> authorities;

    //获取当前认证账户的角色和权限信息
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (!CollectionUtil.isEmpty(authorities)) {
            return authorities;
        }
        authorities = new ArrayList<>();
        Role role = worker.getRole();
        authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        Set<Permission> permissions = role.getPermissions();
        if(role.getPermissions()!=null){
            permissions.stream().forEach(permission -> {
                authorities.add(new SimpleGrantedAuthority(permission.getPermissionName()));
            });
        }
        return authorities;
}

    @Override
    public String getPassword() {
        return worker.getPassword();
    }

    @Override
    public String getUsername() {
        return worker.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;
    }
}

        这里的Worker对象就是数据库中对应包含角色和权限信息的实体类,一个用户可以有多个角色,这里我直接就写了一个角色,一个角色对应多个权限。注意角色的唯一标识需要以ROLE_开头。下方可根据需求设置,这里统一返回true。这些判断会在认证过程中依次执行判断,若返回false则认证失败。

Token过滤器Filter

/**
 * * 解析请求头中的token,并验证合法性。
 * * 继承 OncePerRequestFilter 保证请求经过过滤器一次
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private RedisUtil redisUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String url = request.getRequestURI();
        if (url.equals("/system/login")) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }

        //1.从请求头中获取token数据
        String token = request.getHeader(jwtProperties.getTokenName());
        //2.验证token合法性
        log.info("验证token合法性:{}", token);
        Long id = null;
        try {
        Map<String, Object> map = JwtUtil.parseToken(jwtProperties.getSecretKey(), token);
        //直接object转Long会报错
            id = Long.parseLong(map.get("id").toString());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Token已过期,请重新登陆");
        }
        //从redis中获取用户信息
        log.info("当前用户id:{}", id);
        ThreadLocalUtil.setCurrentId(id);
        Object worker = redisUtil.get(RedisKeyConstant.WORKER_PREFIX + "_" + id);
        if(ObjectUtil.isNull(worker)){
            throw new AccountNotFoundException("缓存中用户不存在,请重新登录");
        }
        Worker w = (Worker)worker;
        LoginUser loginUser = new LoginUser(w);

        //4.将Authentication对象(用户信息、已认证状态、权限信息)存入 SecurityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser , null , loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

UserDetails登录业务

@Service
@Slf4j
public class SecurityUserDetailService implements UserDetailsService {

    @Autowired
    private WorkerMapper workerMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StringUtils.isEmpty(username)) {
            throw new RuntimeException("用户名不存在");
        }
        Worker worker = workerMapper.selectAll(phone);
        if (ObjectUtils.isEmpty(worker)) {
            throw new RuntimeException("账号不存在");
        }
        LoginUser loginUser = new LoginUser(worker);
        return loginUser;
    }
}

        这里是查询数据库获取用户信息的地方,通过Mapper编写SQL查询用户的角色和权限信息,用户和角色是一对多(我这里写的是一对一),角色和权限也是一对多。

用户登录业务逻辑

    public WorkerLoginVO login(String username,String password) {
        //密码登录
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
        Worker worker = loginUser.getWorker();
        //Redis缓存权限信息
        redisUtil.set(RedisKeyConstant.WORKER_PREFIX + "_" + worker.getId(), worker, 6 * 60 * 60L);
        //生成token,下面是自己的其他业务
        HashMap<String, Object> claims = new HashMap<>();
        claims.put("id", worker.getId());
        String token = JwtUtil.genToken(jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims);
        WorkerLoginVO workerLoginVO = WorkerLoginVO.builder().token(token).roleId(worker.getRoleId()).build();
        return workerLoginVO;
    }

认证授权的实现逻辑有多种方式,此处仅展示整个认证的过程,应根据自己业务需求来合理制定。

标签:return,Spring,worker,认证,response,new,Security,public
From: https://blog.csdn.net/qq_52464274/article/details/140673612

相关文章

  • SpringBoot 配置文件详解:properties 和 yml
    目录一、配置文件的作用二、配置文件的格式三、properties配置文件说明 3.1 properties基本语法3.2读取配置文件四、yml配置文件说明4.1yml基本语法4.2yml读取文件4.3yml使用进阶4.3.1配置对象4.3.2配置集合4.3.3配置Map一、配置文件的作用配置文......
  • 为啥Spring原理中依赖注入之后,再做AOP代理依赖注入将失效
    在java中,依赖注入(DependencyInjection,简称DI)是一种设计模式,它通过将对象的依赖关系从代码中移除,而是通过外部容器来管理和注入依赖。这样可以提高代码的可维护性和可测试性。AOP(Aspect-OrientedProgramming,面向切面编程)是另一种编程范式,它允许开发者在不修改原有代码的情......
  • Spring Boot学习|Stopwatch 在 Spring Boot 中的使用
    文章目录什么是Stopwatch?使用场景优点缺点注意事项使用步骤使用案例及结果可能面试题1.**理解与解释**2.**技术细节**3.**实际应用**4.**优缺点与替代方案**5.**面向框架的具体问题**6.**高级主题**什么是Stopwatch?Stopwatch是由ApacheCommonsLang......
  • SpringBoot自动配置(面试重点)
    自动配置是指:自动配置是指在应用程序启动时,SpringBoot根据classpath路径下的jar包自动配置应用程序所需的一系列bean和组件,从而减少开发者的配置工作,提高开发效率。一:ConditionCondition是spring4.0之后添加的条件判断功能,通过这个功能可以实现选择性的创建Bean操作。Condit......
  • SpringBoot整合Swagger2,代码文档一手抓
    文章目录引言什么是swaggerSwagger的优势SpringBoot整合Swagger2添加Swagger依赖application.yml配置配置类SwaggerConfig启动类配置RESTful实战案例参考常见swagger注解说明页面访问效果常见错误引言什么是swaggerSwagger是一个规范且完整的框架,用于生成、描......
  • Springboot整合redis
    引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>修改配置文件//单机模式配置spring.redis.host=172.16.7.21 //ip地址spring.redis.port=6379 //端口号s......
  • 求职面试 - Spring 面试知识点
    Spring面试知识点1.Spring特点Spring主要有如下特点:轻量级:Spring是非侵入式,其中的对象不依赖Spring的特定类;控制反转(IoC):通过IoC,促进了低耦合,一个对象依赖的其他对象通过被动的方式传递进来,而不用该对象主动创建或查找;面向切面(AOP):支持面向切面编程,将应用业务逻辑......
  • Java学习 - Springboot 集成 Security 入门小实例
    前言SpringSecurity是Spring家族中一个强大可定制的身份验证和访问控制框架,和Shiro一样,它们都具有认证、授权、加密等用于权限管理的功能。但相比于Shiro,SpringSecurity的功能无疑更加强大。而且作为Spring家族中的一份子,配合家族中的其它兄弟-SpringBoot、S......
  • 使用elasticjob-lite-spring-boot-starter 3.0.1,“事件追踪“不起作用问题,
    版本<dependency><groupId>org.apache.shardingsphere.elasticjob</groupId><artifactId>elasticjob-lite-spring-boot-starter</artifactId><version>3.0.1</version></dependency>解决方案增加配置 overwrite源码的原因:如果ove......
  • Spring框架配置扩展
    Spring框架的配置扩展是提高应用程序灵活性和可维护性的关键部分。通过不同的配置方法,我们可以更好地管理数据源、拆分配置文件、自动装配Bean以及控制Bean的作用域1.配置数据源数据源配置是Spring应用程序中的关键部分,它直接影响到数据库操作的性能和管理(1)使用proper......