前端可以根据权限信息控制菜单和页面展示,操作按钮的显示。但这并不够,如果有人拿到了接口,绕过了页面直接操作数据,这是很危险的。所以我们需要在后端也加入权限控制,只有拥有操作权限,该接口才能被授权访问。

在进入Controller方法前判断当前用户是否拥有访问权限,可以通过Filter加AOP的方式实现认证和授权。本次介绍的是成熟的框架:Spring Security。其他框架还有Shiro等。

Spring Security简介

Spring Security的重要核心功能功能是“认证”和“授权”,即用户认证(Authentication)用户授权(Authorization)两部分:

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求提供用户名和密码,系统通过校验用户名和密码来完成认证过程。

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,用的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

Spring Security的特点:

  • 和Spring无缝整合
  • 全面的权限控制
  • 专门为Web开发而设计
  • 重量级

Spring Boot出现后,其为Spring Security提供了自动配置方案,可以使用少量的配置来使用Spring Security。如果你的项目是基于Spring Boot的,使用Spring Security无疑是很棒的选择!

Spring Security实现权限

要对Web资源进行保护,最好的办法莫过于Filter

要对方法调用进行保护,最好的方法莫过于AOP

Spring Security进行认证和鉴权的时候就是利用一系列的Filter进行拦截的。

如图所示,一个请求要想访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分就是负责异常处理,橙色部分则是负责授权。经过一系列拦截最终访问到我们的API。

  • FilterSecurityInterceptor:是一个方法级的过滤器,基本位于过滤链的最底部。
  • ExceptionTranslationFilter:是一个异常过滤器,用来处理在认证授权过程中抛出的异常。
  • UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中用户名、密码。

这里我们只需要重点关注两个过滤器即可:UsernamePasswordAuthenticationFilter负责登陆认证,FilterSecurityInterceptor负责权限授权。

说明:Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security!这个框架的使用方式就是对这些过滤器和组件进行扩展。

用户认证流程

自定义组件

根据认证流程,我们需要自定义以下组件:

  • UserDetails
  • loadUserByUsername
  • passwordEncoder

1、登陆Filter,判断用户名和密码是否正确,生成token

2、认证解析token组件,判断请求头是否有token,如果有认证完成

3、在配置类配置相关认证类

代码实现

完整项目地址:Server | GitHub

依赖

创建一个spring-security模块(module),可以放在项目的common模块下

创建完成,导入相关的Maven依赖

<dependencies>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

工具类

ResponseUtil

用于写会数据给前端

import com.fasterxml.jackson.databind.ObjectMapper;
import com.swx.common.pojo.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseUtil {

    public static void out(HttpServletResponse response, R r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

JwtHelper

package com.swx.common.jwt;

import io.jsonwebtoken.*;
import org.springframework.util.StringUtils; import java.util.Date;
public class JwtHelper {

    private static long tokenExpiration = 60 * 60 * 1000;
private static String tokenSignKey = "xxxxxx"; public static String createToken(Long userId, String username) {
return Jwts.builder()
.setSubject("AUTH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("username", username)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact(); } public static Long getUserId(String token) {
try {
if (StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
String userId = body.get("userId").toString();
return Long.parseLong(userId);
} catch (Exception e) {
e.printStackTrace();
return null;
}
} public static String getUsername(String token) {
try {
if (StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return (String) body.get("username");
} catch (Exception e) {
e.printStackTrace();
return null;
}
} }

自定义UserDetail

继承UserDetail的User,其中sysUser是项目数据库的实体类

import com.swx.model.system.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User; import java.util.Collection;
public class CustomUser extends User {

    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
super(sysUser.getUsername(), sysUser.getPassword(), authorities);
this.sysUser = sysUser;
} public SysUser getSysUser() {
return sysUser;
} public void setSysUser(SysUser sysUser) {
this.sysUser = sysUser;
}
}

自定义解码器

用于匹配前端传过来的密码和数据库中的密码是否一致,其中MD5.encrypt是自定义的MD5加密工具

MD5:MD5 | GitHub

import com.swx.common.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
} @Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}

自定义UserDetailsService

该类的实现类会查询项目的数据库,根据用户名获取用户信息,包括密码等,用于匹配和授权。

注意要继承org.springframework.security.core.userdetails.UserDetailsService

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public interface UserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService { /**
* 根据用户名获取用户对象,获取不到直接抛异常
*/
@Override
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

实现该类

该实现可以放到项目的service.impl中,就像项目其他Service的实现类一样

SysUserService:SysUserServiceImpl | GitHub

SysMenuService:SysMenuServiceImpl | GitHub

Permission:Permission | GitHub

Import
import com.swx.auth.service.SysMenuService;
import com.swx.auth.service.SysUserService;
import com.swx.model.system.SysUser;
import com.swx.security.custom.CustomUser;
import com.swx.security.custom.UserDetailsService;
import com.swx.vo.system.Permission;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService { private final SysUserService sysUserService;
private final SysMenuService sysMenuService; public UserDetailsServiceImpl(SysUserService sysUserService, SysMenuService sysMenuService) {
this.sysUserService = sysUserService;
this.sysMenuService = sysMenuService;
} /**
* 根据用户名获取用户对象,获取不到直接抛异常
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询
SysUser sysUser = sysUserService.getUserByUsername(username);
if (null == sysUser) {
throw new UsernameNotFoundException("用户名不存在!");
}
if (sysUser.getStatus() == 0) {
throw new DisabledException("disable");
} // 查询权限列表
List<Permission> permissions = sysMenuService.queryUserAuthListByUserId(sysUser.getId());
// 封装Spring Security的权限类型
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
permissions.forEach(permission -> {
authorities.add(new SimpleGrantedAuthority(permission.getAuth().trim()));
});
return new CustomUser(sysUser, authorities);
}
}

拦截器

TokenLoginFilter

获得输入的用户名和密码,封装成框架要求的对象,调用认证方法。认证成功则将权限信息存入Redis,并返回Token给前端。

该类继承UsernamePasswordAuthenticationFilter,实现登陆的拦截校验。

点击查看代码
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.swx.common.jwt.JwtHelper;
import com.swx.common.pojo.R;
import com.swx.common.pojo.ResultCode;
import com.swx.common.utils.ResponseUtil;
import com.swx.security.custom.CustomUser;
import com.swx.vo.system.LoginVo;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
/**
* 获得输入的用户名和密码,封装成框架要求的对象,调用认证方法
*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private final RedisTemplate<String, String> redisTemplate;
// 构造方法
public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
this.setAuthenticationManager(authenticationManager);
this.setPostOnly(false);
// 指定登陆接口及提交方式,可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login", "POST"));
} // 登陆认证
// 获取输入的用户名和密码,调用方法认证
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
// 获取用户信息
LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
// 封装对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
// 调用方法
return this.getAuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
} // 认证成功调用的方法
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 获取当前用户
CustomUser customUser = (CustomUser) authResult.getPrincipal();
// 生成Token
String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());
// 获取当前用户的权限数据,放到Redis中,key: username value: permissions
redisTemplate.opsForValue().set(
customUser.getUsername(),
JSON.toJSONString(customUser.getAuthorities()));
// 返回
HashMap<String, Object> map = new HashMap<>();
map.put("token", token);
ResponseUtil.out(response, R.success(map));
} // 认证失败调用的方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
// 封装错误信息,用于返回
R r = R.fail(ResultCode.LOGIN_AUTH_FAIL);
Throwable ex = failed.getCause();
if (ex instanceof DisabledException) {
r.setResultCode(ResultCode.USER_DISABLE);
} else if (failed instanceof UsernameNotFoundException || failed instanceof BadCredentialsException) {
r.setResultCode(ResultCode.USER_LOGIN_ERROR);
}
ResponseUtil.out(response, r);
}
}

TokenAuthenticationFilter

判断是否完成认证,将认证信息保存到Security上下文中

Import
import com.alibaba.fastjson2.JSON;
import com.swx.common.jwt.JwtHelper;
import com.swx.common.pojo.R;
import com.swx.common.pojo.ResultCode;
import com.swx.common.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 判断是否完成认证
*/
public class TokenAuthenticationFilter extends OncePerRequestFilter { private final RedisTemplate<String, String> redisTemplate; public TokenAuthenticationFilter(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
} @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 如果是登陆接口,直接放行
if ("/admin/system/index/login".equals(request.getRequestURI())) {
chain.doFilter(request, response);
return;
} UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if (null != authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} else {
ResponseUtil.out(response, R.fail(ResultCode.LOGIN_AUTH_FAIL));
} } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (!StringUtils.isEmpty(token)) {
String username = JwtHelper.getUsername(token);
if (!StringUtils.isEmpty(username)) {
// 从redis中获取权限数据
String authString = redisTemplate.opsForValue().get(username);
if (!StringUtils.isEmpty(authString)) {
List<Map> mapList = JSON.parseArray(authString, Map.class);
System.out.println(mapList);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
mapList.forEach(map -> {
String authority = (String) map.get("authority");
authorities.add(new SimpleGrantedAuthority(authority));
});
return new UsernamePasswordAuthenticationToken(username, null, authorities);
} else {
return new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
}
}
}
return null;
}
}

配置文件

创建一个Spring Security的配置文件,开启相关的注解

Import
import com.swx.security.custom.CustomMd5PasswordEncoder;
import com.swx.security.filter.TokenAuthenticationFilter;
import com.swx.security.filter.TokenLoginFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService; private final CustomMd5PasswordEncoder customMd5PasswordEncoder; private final RedisTemplate<String, String> redisTemplate; public WebSecurityConfig(UserDetailsService userDetailsService, CustomMd5PasswordEncoder customMd5PasswordEncoder, RedisTemplate<String, String> redisTemplate) {
this.userDetailsService = userDetailsService;
this.customMd5PasswordEncoder = customMd5PasswordEncoder;
this.redisTemplate = redisTemplate;
} @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers("/admin/system/index/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
.addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate));
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(customMd5PasswordEncoder);
} @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/favicon.icon", "/swagger-resources/**", "webjars/**", "/v2/**", "swagger-ui.html/**", "doc.html");
}
}

食用教程

可以在业务模块中导入pom信息

<dependency>
<groupId>com.swx</groupId>
<artifactId>spring-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

在需要授权的接口上加入注解,就像这样

@Api(tags = "角色管理接口")
@RestController
@ResponseResult
@RequestMapping("/admin/system/sysRole")
public class SysRoleController { private final SysRoleService sysRoleService; public SysRoleController(SysRoleService sysRoleService) {
this.sysRoleService = sysRoleService;
} @ApiOperation("为用户分配角色")
@PreAuthorize("hasAuthority('system_role_assign')")
@PostMapping("/doAssign")
public void doAssign(@RequestBody AssignRoleVo assignRoleVo) {
sysRoleService.doAssign(assignRoleVo);
} @ApiOperation("查询所有角色")
@PreAuthorize("hasAuthority('system_role_list')")
@GetMapping("/findAll")
public List<SysRole> findAll() {
return sysRoleService.list();
} /**
*
* @param page 当前页
* @param limit 记录数
* @param sysRoleQueryVo 查询参数
* @return 分页信息
*/
@ApiOperation("条件分页查询")
@PreAuthorize("hasAuthority('system_role_list')")
@GetMapping("{page}/{limit}")
public IPage<SysRole> pageQueryRole(@PathVariable Long page,
@PathVariable Long limit,
SysRoleQueryVo sysRoleQueryVo) {
// 自定义Page,修改current为page,和前端保持一致
CustomPage<SysRole> pageParam = new CustomPage<>(page, limit);
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
String roleName = sysRoleQueryVo.getRoleName();
if (!StringUtils.isEmpty(roleName)) {
wrapper.like(SysRole::getRoleName, roleName);
}
IPage<SysRole> iPage = sysRoleService.page(pageParam, wrapper);
return iPage;
} @ApiOperation("添加角色")
@PreAuthorize("hasAuthority('system_role_add')")
@PostMapping("")
public void save(@RequestBody SysRole role) {
boolean save = sysRoleService.save(role);
if (!save) {
throw new BizException("添加失败");
}
} /**
* 根据id查询角色
* @param id 角色id
* @return 角色
*/
@ApiOperation("根据ID查询")
@PreAuthorize("hasAuthority('system_role_list')")
@GetMapping("{id}")
public SysRole get(@PathVariable Long id) {
return sysRoleService.getById(id);
} /**
* 更新角色
* @param role 角色信息
*/
@ApiOperation("更新角色")
@PreAuthorize("hasAuthority('system_role_update')")
@PutMapping("")
public void update(@RequestBody SysRole role) {
boolean update = sysRoleService.updateById(role);
if (!update) {
throw new BizException("更新失败");
}
} @ApiOperation("根据id删除")
@PreAuthorize("hasAuthority('system_role_remove')")
@DeleteMapping("{id}")
public void delete(@PathVariable Long id) {
boolean delete = sysRoleService.removeById(id);
if (!delete) {
throw new BizException("删除失败");
}
} @ApiOperation("批量删除")
@PreAuthorize("hasAuthority('system_role_remove')")
@DeleteMapping("batch")
public void batchRemove(@RequestBody List<Long> ids) {
boolean delete = sysRoleService.removeByIds(ids);
if (!delete) {
throw new BizException("删除失败");
}
} }

单体项目使用Spring Security实现登陆认证授权的更多相关文章

  1. Spring Security OAuth2.0认证授权四:分布式系统认证授权

    Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...

  2. Spring Security OAuth2.0认证授权六:前后端分离下的登录授权

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  3. Spring Security OAuth2.0认证授权二:搭建资源服务

    在上一篇文章[Spring Security OAuth2.0认证授权一:框架搭建和认证测试](https://www.cnblogs.com/kuangdaoyizhimei/p/14250374. ...

  4. Spring Security OAuth2.0认证授权三:使用JWT令牌

    Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...

  5. Spring Security OAuth2.0认证授权五:用户信息扩展到jwt

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  6. Spring Security OAuth2.0认证授权一:框架搭建和认证测试

    一.OAuth2.0介绍 OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不 需要将用户名和密码提供给第三方应用或分享他们数据的所有内容. 1.s ...

  7. Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)

    基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...

  8. Spring security OAuth2.0认证授权学习第三天(认证流程)

    本来之前打算把第三天写基于Session认证授权的,但是后来视屏看完后感觉意义不大,而且内容简单,就不单独写成文章了; 简单说一下吧,就是通过Servlet的SessionApi 通过实现拦截器的前置 ...

  9. Spring security OAuth2.0认证授权学习第一天(基础概念-认证授权会话)

    这段时间没有学习,可能是因为最近工作比较忙,每天回来都晚上11点多了,但是还是要学习的,进过和我的领导确认,在当前公司的技术架构方面,将持续使用Spring security,暂不做Shiro的考虑, ...

  10. Spring security OAuth2.0认证授权学习第二天(基础概念-RBAC)

    RBAC 基于角色的访问控制 基于角色的访问控制用代码实现一下其实就是一个if的问题if(如果有角色1){ } 如果某个角色可以访问某个功能,当某一天其他的另一个角色也可以访问了,那么代码就需要变化, ...

随机推荐

  1. TensorRT C# API 项目更新 (1):支持动态Bath输入模型推理

    1. 项目介绍    NVIDIA TensorRT 是一款用于高性能深度学习推理的 SDK,包括深度学习推理优化器和运行时,可为推理应用程序提供低延迟和高吞吐量.基于 NVIDIA TensorRT ...

  2. Avalonia 中的样式和控件主题

    在 Avalonia 中,样式是定义控件外观的一种方式,而控件主题则是一组样式和资源,用于定义应用程序的整体外观和感觉.本文将深入探讨这些概念,并提供示例代码以帮助您更好地理解它们. 样式是什么? 样 ...

  3. 抓包整理————tcp 三次握手性能优化[十]

    前言 tcp 三次握手性能优化. 正文 服务器三次握手流程示例: 下面就是3次握手的过程: 知道这个有什么用呢? 我举一个我使用到的例子哈. 比如有很多 tcp 连接到一台机器上机器上,那么tcp_m ...

  4. MUI-拿到版本名称和版本code

    场景: 一般App发布了新版本之后,都会提示用户去更新.这就需要我们获取App的当前版本号与后台存储的版本号做对比来判断是否需要更新. 获取版本名称和版本code: 打开manifest.json文件 ...

  5. few-shot-learning for object detection

    github  https://github.com/LiuXinyu12378/few-shot-learning-for-object-detection train.py from __futu ...

  6. 《Effective C#》系列之(五)——优化集合的使用

    一.优化集合的使用 在<Effective C#>这本书中,优化集合的使用是其中一章的内容.以下是该章节的一些核心建议,以及使用C#代码示例说明: 使用泛型集合:泛型集合可以避免装箱和拆箱 ...

  7. 飞天大数据产品价值解读— SaaS模式云数据仓库MaxCompute

    飞天大数据产品价值解读 - SaaS模式云数据仓库 MaxCompute摘要:企业在数字化转型过程中面临数据技术平台建设和运营的诸多挑战,随着现代化数据仓库向多功能.服务化方向发展演进,技术侧的变革为 ...

  8. 基于 Observable 构建前端防腐策略

    ​简介:To B 业务的生命周期与迭代通常会持续多年,随着产品的迭代与演进,以接口调用为核心的前后端关系会变得非常复杂.在多年迭代后,接口的任何一处修改都可能给产品带来难以预计的问题.在这种情况下,构 ...

  9. 为了让你在“口袋奇兵”聊遍全球,Serverless 做了什么?

    简介: 江娱互动是一家新兴的游戏企业,自 2018 年成立伊始,江娱互动就面向广阔的全球游戏市场,通过创造有趣的游戏体验,在竞争激烈的游戏市场占得一席之地.仅仅 2 年的时间,江娱互动就凭借 Topw ...

  10. 云原生消息、事件、流超融合平台——RocketMQ 5.0 初探

    简介: 今天分享的主题是云原生消息事件流超融合平台 RocketMQ 5.0 初探,内容主要分为三个部分: 首先,带大家回顾业务消息领域首选 RocketMQ 4 发展历史以及 4.x 版本的演进与发 ...