当前位置: 首页 > news >正文

嘉定北京网站建设怎样做企业宣传推广

嘉定北京网站建设,怎样做企业宣传推广,政府网站建设工作会议,自己做的个人网站 csdn【SpringSecurity】认证授权框架——SpringSecurity使用方法 文章目录【SpringSecurity】认证授权框架——SpringSecurity使用方法1. 概述2. 准备工作2.1 引依赖2.2 测试3. 认证3.1 认证流程3.2 登录校验问题3.3 实现3.3.1 实现UserDetailsService接口3.3.2 密码存储和校验3.3.…

【SpringSecurity】认证授权框架——SpringSecurity使用方法

文章目录

  • 【SpringSecurity】认证授权框架——SpringSecurity使用方法
    • 1. 概述
    • 2. 准备工作
      • 2.1 引依赖
      • 2.2 测试
    • 3. 认证
      • 3.1 认证流程
      • 3.2 登录校验问题
      • 3.3 实现
        • 3.3.1 实现UserDetailsService接口
        • 3.3.2 密码存储和校验
        • 3.3.3 自定义登录接口
          • 3.3.3.1 配置
          • 3.3.3.2 定义登录方法
        • 3.3.4 自定义认证过滤器
          • 3.3.4.1 确定过滤器顺序
        • 3.3.5 自定义退出登录接口
    • 4. 授权
      • 4.1 授权流程
      • 4.2 实现
        • 4.2.1 开启全局权限功能
        • 4.2.2 封装权限信息
      • 4.3 自定义权限校验方法
      • 4.4 基于配置的权限控制
    • 5. 自定义失败处理
      • 5.1 认证失败处理器
      • 5.2 权限不足处理器
      • 5.3 配置
    • 6. 跨域问题

1. 概述

Spring Security是一个框架,提供 认证(authentication)、授权(authorization)和保护,以抵御常见的攻击。它对保护命令式和响应式应用程序有一流的支持,是保护基于Spring的应用程序的事实标准。它还提供了与其他库的集成,以简化其使用。

SpringSecurity的详细使用方法可见:Spring Security 中文文档 :: Spring Security Reference (springdoc.cn)

写这篇文章主要是为了复习SpringSecurity的相关知识,在这里也向大家推荐b站up主 三更草堂 ,他讲的SpringSecurity课程非常棒!


2. 准备工作

2.1 引依赖

创建springboot工程中引入这些依赖:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--主要是这个依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
</dependencies>

2.2 测试

引入好依赖之后就可以启动应用了,我们访问应用的任何一个接口都会跳转到SpringSecurity的一个默认登录页面(如下所示):

image-20230330210017463

SpringSecurity提供了一个默认的登录接口 /login 和退出接口 /logout 。登录接口的默认用户名为 user ,而密码则打印在了应用的控制台:

image-20230330210243936

使用默认的用户名和控制台上的密码就能够登录系统访问接口了。在正常情况下我们需要禁用这个接口,使用我们自定义的接口。


3. 认证

SpringSecurity的认证机制有很多,比如:

  • Username and Password:使用用户名/密码进行认证
  • OAauth 2.0 Login:使用OpenID Connect 和非标准的OAuth 2.0登录(即GitHub)的OAuth 2.0登录。

我们主要了解第一种方式。SpringSecurity认证通过过滤器链实现:

image-20230330212422514

SpringSecurity的过滤器链包含15个过滤器,上图只展示了较为核心的过滤器。

  • SecurityContextPersistenceFilter:过滤器链的入口和出口,保存和清除 SecurityContextHolder 中的 SecurityContext
  • UsernamePasswordAuthenticationFilter:负责收集我们在登陆页面输入的用户名和密码并封装成Authentication对象。
  • ExceptionTranslationFilter:只处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException ,其他异常继续抛出。
  • FilterSecurityInterceptor:负责权限校验的过滤器。

3.1 认证流程

image-20230330212244925

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ailkTJHA-1680426554649)(null)]

注释:

  • Authentication:是一个接口,它的实现类表示当前访问系统的用户,封装了用户相关信息。
  • AuthenticationManager:也是一个接口,声明了认证Authentication的方法
  • UserDetailsService:还是一个接口,加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
  • UserDetails:接口,提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

3.2 登录校验问题

登录流程

  1. 自定义登录接口替代原有登录接口。

  2. 自定义实现UserDetailService接口的类A,替代原有实现类。

  3. 在类A中中查询数据库进行认证校验。

  4. 校验通过则使用jwt工具类生成token,token生成规则有两种:

    4.1 使用用户id生成token,其余用户信息存入redis。

    4.2 使用用户基本信息的json串(脱敏)生成token,服务端不必存储用户信息。

校验流程

  1. 自定义jwt认证过滤器。

  2. 解析token。

    2.1 解析获得用户id,根据用户id从redis中获得用户信息,存入SecurityContextHolder。

    2.2 解析获得用户信息json串,将其反序列化为用户对象,存入SecurityContextHolder。

在这里我们采用第一种方案,使用redis。


3.3 实现

3.3.1 实现UserDetailsService接口

创建UserDetailsService接口的实现类,在其中对用户进行认证和授权。

@Service
public class UserDetailServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//认证LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(StringUtils.isNotBlank(username), User::getUserName, username);User user = userMapper.selectOne(queryWrapper);if (Objects.isNull(user)) {throw new RuntimeException("用户名或密码错误");}//todo 查询对应的权限信息//把数据封装成UserDetails返回UserDetails userDetails = org.springframework.security.core.userdetails.User.withUsername(user.getUserName()).password(user.getPassword()).authorities(new String[]{}).build();return userDetails;}
}

除了使用security提供的生成UserDetails对象的方法外,我们还可以选择自定义。

我们新创建一个类 LoginUser 实现 UserDetails 接口。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

使用我们自定义的类去创建UserDetails对象。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//认证LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(StringUtils.isNotBlank(username), User::getUserName, username);User user = userMapper.selectOne(queryWrapper);if (Objects.isNull(user)) {throw new RuntimeException("用户名或密码错误");}//todo 查询对应的权限信息//把数据封装成UserDetails返回return new LoginUser(user);
}

3.3.2 密码存储和校验

我们不会在数据库中使用明文的方式存储密码,我们需要存储的是加密之后的密码。

SpringSecurity默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。这种方式不太行,所以我们需要使用BCryptPasswordEncoder去替代原来的PasswordEncoder。

我们只需要定义一个SpringSecurity的配置类,并且让这个类继承 WebSecurityConfigurerAdapter

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {//创建 BCryptPasswordEncoder 对象并注入容器@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

3.3.3 自定义登录接口

创建一个控制器 LoginController ,在其中定义一个登录接口:

@RestController
public class LoginController {@Autowiredprivate UserService userService;@PostMapping("/user/login")public ResponseResult login(@RequestBody User user) {return userService.login(user);}
}
3.3.3.1 配置

我们需要让SpringSecurity对自定义的接口放行,让用户不用登陆也可以访问。然后在 login(user) 中调用AuthenticationManager的authenticate方法来进行用户认证,所以需要在SpringSecurity配置类中进行以下两点配置:

  1. 对自定义登录接口放行
  2. AuthenticationManager 对象注入容器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {//创建 BCryptPasswordEncoder 对象并注入容器@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}//注入AuthenticationManager对象@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//对登录接口放行@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问(未登录时可以访问,登陆后不能访问).antMatchers("/user/login").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();}
}

3.3.3.2 定义登录方法

在用户服务接口的实现类中定义 login(user) 方法:

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic ResponseResult login(User user) {//调用 AuthenticationManager 对象的 authenticate() 方法进行用户认证UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());Authentication authenticate = null;try {authenticate = authenticationManager.authenticate(authenticationToken);} catch (Exception e) {throw new RuntimeException(e.getMessage());}if (authenticate == null) {throw new RuntimeException("登陆失败");}//如果认证未通过,则给出对应提示//通过,则使用userid生成一个jwt,jwt存入ResponseResultLoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userId = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userId);Map<String, String> map = new HashMap<>();map.put("token", jwt);//把用户信息存入redisstringRedisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(loginUser));return new ResponseResult(200, "登录成功", map);}
}

3.3.4 自定义认证过滤器

我们需要一个认证过滤器,作用是取出每次请求的请求头,看它是否携带 token 。再对 token 进行校验,判断用户的登录状态。

@Component
public class JwtAuthenticactionTokenFilter extends OncePerRequestFilter {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取tokenString token = request.getHeader("token");if (StringUtils.isBlank(token)) {//没有token//放行,让后面的认证过滤器去拒绝filterChain.doFilter(request, response);return;}//解析tokenString userId;try {Claims claims = JwtUtil.parseJWT(token);userId = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token非法");}//拼接redis的keyString key = "login:" + userId;String userJson = stringRedisTemplate.opsForValue().get(key);if (StringUtils.isBlank(userJson)) {throw new RuntimeException("用户未登录");}LoginUser loginUser = JSON.parseObject(userJson, LoginUser.class);//存入SecurityContextHolder//todo 还未授权UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}

3.3.4.1 确定过滤器顺序

定义了认证过滤器后,我们还需要确定过滤器的执行顺序,我们必须保证这个过滤器的执行在Security的认证过滤器之前,所以我们把他放在 UsernamePasswordAuthenticationFilter 之前比较合适。

找到之前的配置类,从容器中取出自定义的 JwtAuthenticactionTokenFilter 过滤器并在 configure() 方法中加入最后一行代码。

@Autowired
private JwtAuthenticactionTokenFilter jwtAuthenticactionTokenFilter;protected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问(未登录时可以访问,登陆后不能访问).antMatchers("/user/login").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//把自定义认证过滤器放在 UsernamePasswordAuthenticationFilter 过滤波器前面http.addFilterBefore(jwtAuthenticactionTokenFilter, UsernamePasswordAuthenticationFilter.class);
}

3.3.5 自定义退出登录接口

退出流程:

  1. 通过 SecurityContextHolder 获得用户id
  2. 利用用户id凭借redis的key值
  3. 通过key值把redis中的用户信息删除

首先定义一个退出接口:

@RequestMapping("/user/logout")
public ResponseResult logout() {return userService.logout();
}

在实现类中定义 logout() 方法:

@Override
public ResponseResult logout() {//获取SecurityContextHolder中的用户idAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();Long id = loginUser.getUser().getId();//删除redis中的值stringRedisTemplate.delete("login:" + id);return new ResponseResult(200, "退出成功");
}

4. 授权

4.1 授权流程

在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。


4.2 实现

4.2.1 开启全局权限功能

@EnableGlobalMethodSecurity(prePostEnabled = true)

开启功能之后,我们就可以在接口方法上面加上 @PreAuthorize 注解了。

@RequestMapping("/hello")
@PreAuthorize("hasAuthority('test')")
public String hello() {return "hello";
}

4.2.2 封装权限信息

权限的封装是在登录流程时发生的,所以我们在登录方法中把权限信息封装进入 UserDetails 的实现类中:

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;//权限字符串集合private List<String> permissions;//权限集合,不用序列化到redis@JSONField(serialize = false)private Set<SimpleGrantedAuthority> authorities;public LoginUser(User user, List<String> permissions) {this.user = user;this.permissions = permissions;}//将权限字符串集合封装成权限集合@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if (authorities == null) {authorities = permissions.stream().map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toSet());}return authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

修改完毕,我们先在登录校验方法 loadUserByUsername(String username) 中查询数据库得到权限字符串集合:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//认证LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(StringUtils.isNotBlank(username), User::getUserName, username);User user = userMapper.selectOne(queryWrapper);if (Objects.isNull(user)) {throw new RuntimeException("用户名或密码错误!");}//查询对应的权限信息List<String> list = menuMapper.selectPermsByUserId(user.getId());return new LoginUser(user, list);
}

然后在自定义过滤器中补充权限设置:

UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());

4.3 自定义权限校验方法

Security提供的权限校验方法比较单一,且不灵活。所以我们可以自定义权限校验方法并且在注解中使用我们的方法。

1)自定义权限校验方法

创建一个类并注入容器中,在其中编写我们的校验方法:

@Component(value = "ex")
public class ExpressionRoot {public boolean hasAuthority(String authority) {//获取当前用户的权限Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();List<String> permissions = loginUser.getPermissions();//判断用户是否拥有访问权限return permissions.contains(authority);}
}

2)在注解中使用我们的方法

@RequestMapping("/hello")
//@PreAuthorize("hasAuthority('system:dept:list')")
@PreAuthorize("@ex.hasAuthority('system:dept:list')")
public String hello() {return "hello";
}

4.4 基于配置的权限控制

我们上面的示例都是基于注解的权限控制,我们也可以通过配置来进行权限的控制,如下所示:

 http.authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()//基于配置的权限控制.antMatchers("/testCors").hasAuthority("system:dept:list222")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();

5. 自定义失败处理

我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

​ 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

​ 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。


5.1 认证失败处理器

自定义认证失败处理器:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败请重新登录");String json = JSON.toJSONString(result);//处理异常WebUtils.renderString(httpServletResponse, json);}
}

WebUtils 工具类中对响应进行封装,具体方法如下:

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

5.2 权限不足处理器

自定义权限不足处理器:

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");String json = JSON.toJSONString(result);//处理异常WebUtils.renderString(httpServletResponse, json);}
}

5.3 配置

我们将这两个处理器注入容器,再将其配置到Security中:

@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;@Autowired
private AccessDeniedHandler accessDeniedHandler;//对登录接口放行
@Override
protected void configure(HttpSecurity http) throws Exception {//省略其他配置......//配置异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)//认证失败处理器.accessDeniedHandler(accessDeniedHandler);//授权失败处理器
}

6. 跨域问题

只有浏览器和服务端的之间的请求有跨域问题,服务端和服务端之间是不存在跨域问题的。

我们首先在我们的springboot项目中进行跨域配置:

@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("GET", "POST", "DELETE", "PUT")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}
}

由于我们的资源都会收到SpringSecurity的保护,所以想要跨域访问还要让SpringSecurity运行跨域访问。

然后在security的配置类中添加跨域配置:

@Override
protected void configure(HttpSecurity http) throws Exception {//省略其他配置......//允许跨域http.cors();
}
http://www.shuangfujiaoyu.com/news/44587.html

相关文章:

  • 珠海企业网站建设报价谷歌推广怎么做
  • 上那个网站找手工活做营销软件网站
  • 网页设计制作网站用什么软件网站源码下载
  • 怎么做推广和宣传企业做网站网络推广营销方法
  • 建设商城网站的书籍钱北京seo顾问服务公司
  • 各种网址大全网站人多怎么优化
  • wordpress后台无法访问湖南网站建设推广优化
  • 杭州万户网络关键词搜索优化外包
  • 怎么做钓鱼网站盗取qq最新域名查询
  • 做网站要注意哪一点在线一键生成网页
  • 江苏电信网站备案郑州高端网站建设
  • 功能型网站谷歌网址
  • 郑州网站建设价格指数基金排名前十名
  • 这几年做那个网站致富南宁百度快速排名优化
  • 广州荔湾建网站的公司手游推广平台代理
  • 做网站内容都有哪些建站平台有哪些
  • 北京建设委员会网站首页网络媒体软文案例
  • 福清小程序建设公司seo门户网价格是多少钱
  • java调接口做网站百度客服人工电话24小时
  • 专门做情侣装的网站青岛网站快速排名提升
  • 博客网站源码带后台seo关键词优化价格
  • 建设网站需要准备哪些内容搜索引擎网址
  • 贵阳网站建设方案书西安网络优化哪家好
  • 儿童个人网站源码百度推广seo优化
  • 2017主流网站风格如何做百度推广
  • 帝国cms怎么做电影网站百度文库官网首页
  • 小白如何做网站最近新闻摘抄
  • 上海翼成信息科技有限公司做的什么网站上海aso优化公司
  • 室内设计培训班排行榜学校如何做网站优化
  • 网站优化排名服务app注册推广平台