目录
配置文件 application-security.yaml
异常处理UserAuthenticationEntryPoint
关于系统最终想实现的功能:使用token来实现登录校验,用户登录后拿到token,然后将token放入httpHeader,之后每次接口请求都携带token,验证成功才可进行正常访问流程
储备知识
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT 的三个部分依次如下。

Header(头部)
Payload(负载)
Signature(签名)
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{
"alg": "HS256",
"typ": "JWT"
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串。
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
有很多常见的工具类,我这边用的是这个
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
介绍的文章一抓一大把,这边主要说一下他的几个核心东西
是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
发生在 Authentication(认证)之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。

springboot版本 2.7.3 springsecurity 版本 5.7.3
<!-- spring-boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/>
</parent>
<dependencies>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
注意,springsecurity 5.7之后配置方式已经优化,无需再使用继承式的配置,直接bean方式配置即可
@EnableWebSecurity
@EnableConfigurationProperties(AuthProperties.class)
public class WebSecurityConfig {
@Autowired
private SysUserService sysUserService;
@Autowired
private AuthProperties authProperties;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JWTService jwtService;
@Autowired
private CacheManager cacheManager;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
// 基于 token,不需要 csrf
.csrf().disable()
// 基于 token,不需要 session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 下面开始设置权限
.authorizeRequests(authorize -> authorize
.antMatchers(authProperties.getPermitStatic().toArray(new String[0])).permitAll()
.antMatchers(authProperties.getPermitMethod().toArray(new String[0])).permitAll()
// 其他地址的访问均需验证权限
.anyRequest().authenticated())
.addFilter(new JWTAuthenticationFilter(authenticationManager, sysUserService, jwtService, userCache()))
.exceptionHandling().authenticationEntryPoint(new UserAuthenticationEntryPoint()).and()
// 认证用户时用户信息加载配置,注入springAuthUserService
.userDetailsService(sysUserService).build();
}
/**
* 获取AuthenticationManager(认证管理器),登录时认证使用
* @param authenticationConfiguration
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* 密码明文加密方式配置(使用国密SM4)
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new SM4PasswordEncoder();
}
@Bean
UserCache userCache(){
Cache ca = cacheManager.getCache("userCache");
return new SpringCacheBasedUserCache(ca);
}
@Data
@ConfigurationProperties(prefix = "lc.security")
public class AuthProperties {
private JWT jwt;
private List<String> permitStatic;
private List<String> permitMethod;
@Data
public static class JWT{
private Claims claims = new Claims();
private String authHeader;
private String secret;
private Type type = Type.RANDOM;
public void setAuthHeader(String authHeader) {
this.authHeader = authHeader;
}
public String getAuthHeader() {
return authHeader;
}
public Claims getClaims() {
return claims;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getSecret() {
return secret;
}
public void setType(Type type) {
this.type = type;
}
public Type getType() {
return type;
}
public enum Type {
RANDOM, FOREVER
}
@Setter
@Getter
public static class Claims {
private String issuer = "AppName";
private String audience = "Web";
private String subject = "Auth";
private Long expirationTimeMinutes = 60L;
}
}
lc:
security:
#静态资源放行
permit-static:
- /*.html
- /*.html
- /favicon.ico
- /**/*.html
- /**/*.css
- /**/*.js
- /**/*.png
- /**/*.jpg
- /**/*.ttf
- /**/*.woff
- /**/*.wav
- /**/*.gif
- /swagger-ui.html
#方法放行
permit-method:
- /swagger-resources
- /v2/api-docs
- /v3/api-docs
- /api/v1/sys/auth/login
jwt:
auth-header: Authorization
secret: mySecret
type: forever
claims:
issuer: LC
audience: Web
subject: Auth
expiration-time-minutes: 3000
public class UserAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(JSONObject.toJSONString(ReturnVO.failed("登录信息不正确!")));
response.getWriter().flush();
}
}
过滤器JWTAuthenticationFilter
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
private final SysUserService userService;
private final JWTService jwtService;
private final UserCache userCache;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, SysUserService userService, JWTService jwtService, UserCache userCache) {
super(authenticationManager);
this.userService = userService;
this.jwtService = jwtService;
this.userCache = userCache;
}
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtService.getToken(request);
String tokenHeader = request.getHeader("Authorization");
// 如果请求头中没有Authorization信息则直接放行了
if (!StringUtils.hasLength(tokenHeader)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
String username = jwtService.validateToken(token);
if (!StringUtils.hasLength(username)) {
log.error("从token中未获取到用户名, token:{}, URI:{}", token, request.getServletPath());
chain.doFilter(request, response);
return;
}
//从缓存中验证token的存在性
UserInfo user = (UserInfo) userCache.getUserFromCache(username);
if (null == user) {
try {
user = (UserInfo) userService.loadUserByUsername(username);
userCache.putUserInCache(user);
} catch (UsernameNotFoundException e) {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(JSONObject.toJSONString(ReturnVO.failed(e.getMessage())));
response.getWriter().flush();
return;
}
}
// 如果从持久化存储中仍未查到,则执行后续操作,最后返回用户不存在信息到前端
if (null != user) {
// 清空“密码”属性
// 创建验证通过的令牌对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
// 设置令牌到安全上下文中
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
}
public interface JWTService {
/**
* 签名生成
* @param username
* @return
*/
String generateToken(String username);
/**
* 签名检验
* @param token
* @return
*/
String validateToken(String token);
/**
* 签名查询
* @param request
* @return
*/
String getToken(HttpServletRequest request);
}
@Slf4j
@Service
public class JWTServiceImpl implements JWTService {
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512;
private AuthProperties properties;
public JWTServiceImpl(AuthProperties properties) {
this.properties = properties;
}
private Claims getAllClaims(String token) throws AuthTokenException {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(properties.getJwt().getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
throw new AuthTokenException(e.getMessage());
}
return claims;
}
private Date generateExpirationDate() {
return new Date(new Date().getTime() + properties.getJwt().getClaims().getExpirationTimeMinutes() * 60 * 1000);
}
private String getAuthHeader(HttpServletRequest request) {
return request.getHeader(properties.getJwt().getAuthHeader());
}
@Override
public String generateToken(String username) {
return Jwts.builder()
.setIssuer(properties.getJwt().getClaims().getIssuer())
.setSubject(username)
.setAudience(properties.getJwt().getClaims().getAudience())
.setIssuedAt(new Date())
.setExpiration(generateExpirationDate())
.signWith(SIGNATURE_ALGORITHM, properties.getJwt().getSecret())
.compact();
}
@Override
public String validateToken(String token){
Claims allClaims = null;
try {
return getAllClaims(token).getSubject();
} catch (AuthTokenException e) {
log.error(e.getMessage(), e);
}
return null;
}
@Override
public String getToken(HttpServletRequest request) {
return getAuthHeader(request);
}
}
@Slf4j
@Service
public class SysUserService implements UserDetailsService {
@Autowired
private SysUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = userMapper.getUserInfoByUsername(username);
ParamAssert.notNull(userInfo, "用户不存在!");
return userInfo;
}
}
@Data
@ApiModel("用户")
@EqualsAndHashCode(callSuper = true)
public class UserInfo implements UserDetails {
@ApiModelProperty(notes = "用户名")
private String username;
@ApiModelProperty(notes = "姓名")
private String name;
@ApiModelProperty(notes = "编码")
private String code;
@ApiModelProperty(notes = "密码")
private String password;
@ApiModelProperty(notes = "是否启用:true-启用,false-停用")
private boolean enabled = true;
private List<RoleAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
}
启动项目,放开登录接口,登录

返回结果
{
"code": 200,
"message": "SUCCESS",
"data": {
"token": "eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJMQy1NSU5GQU5HIiwic3ViIjoiYWRtaW4iLCJhdWQiOiJXZWIiLCJpYXQiOjE2NjUyMjAzODMsImV4cCI6MTY2NTQwMDM4M30.MhqQl79CgevBw2zeDuL2tsxgZaUe43e16-kw0aWMfCD5Hs9NI_D0dlwwvvr0znlORf6y5eyzyao8EqVIv09URQ"
}
}

我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
我在新的Debian6VirtualBoxVM上安装RVM时遇到问题。我已经安装了所有需要的包并使用下载了安装脚本(curl-shttps://rvm.beginrescueend.com/install/rvm)>rvm,但以单个用户身份运行时bashrvm我收到以下错误消息:ERROR:Unabletocheckoutbranch.安装在这里停止,并且(据我所知)没有安装RVM的任何文件。如果我以root身份运行脚本(对于多用户安装),我会收到另一条消息:Successfullycheckedoutbranch''安装程序继续并指示成功,但未添加.rvm目录,甚至在修改我的.bas
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复