一,为什么使用jwt?

1,什么是jwt?

Json Web Token,

它是JSON风格的轻量级的授权和身份认证规范,

可以实现无状态、分布式的Web应用授权

2,jwt的官网:

https://jwt.io/

java实现的jwt的开源项目:

https://github.com/jwtk/jjwt

3,使用jwt的好处?

客户端请求不依赖服务端的信息,多次向服务端请求不需要必须访问到同一台物理服务器上
服务端的集群和状态对客户端透明
服务端可以任意的迁移和伸缩,方便进行集群化部署
减小服务端存储压力

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/securityjwt

2,项目功能说明:

演示了使用jwt保存用户token,

适用于接口站的用户信息保存

3,项目结构;如图:

三,配置文件说明

1,pom.xml

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!--security begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jjwt begin-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> <!--thymeleaf begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--fastjson begin-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency> <!--jaxb-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency> <!--mysql mybatis begin-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

2,application.properties

#error
server.error.include-stacktrace=always
#error
logging.level.org.springframework.web=trace #thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html #mysql
spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=lhddemo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.mapper

3,数据表:

建表sql:

CREATE TABLE `sys_user` (
`userId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`userName` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
`nickName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵称',
PRIMARY KEY (`userId`),
UNIQUE KEY `userName` (`userName`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'
INSERT INTO `sys_user` (`userId`, `userName`, `password`, `nickName`) VALUES
(1, 'lhd', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '老刘'),
(2, 'admin', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '管理员'),
(3, 'merchant', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '商户老张');

说明:3个密码都是111111,仅供演示使用

CREATE TABLE `sys_user_role` (
`urId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`userId` int(11) NOT NULL DEFAULT '0' COMMENT '用户id',
`roleName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '角色id',
PRIMARY KEY (`urId`),
UNIQUE KEY `userId` (`userId`,`roleName`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表'
INSERT INTO `sys_user_role` (`urId`, `userId`, `roleName`) VALUES
(1, 2, 'ADMIN'),
(2, 3, 'MERCHANT');

四,  java代码说明

1,WebSecurityConfig.java

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource
private UserAuthenticationEntryPoint userAuthenticationEntryPoint; @Autowired
private UserDetailsService jwtUserDetailsService; @Autowired
private JwtRequestFilter jwtRequestFilter; @Resource
private UserAccessDeniedHandler userAccessDeniedHandler; @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
} @Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 本示例不需要使用CSRF
httpSecurity.csrf().disable();
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.authorizeRequests().antMatchers("/home/**").permitAll();
// 认证页面不需要权限
httpSecurity.authorizeRequests().
antMatchers("/auth/authenticate").permitAll().
antMatchers("/admin/**").hasAnyRole("ADMIN").
//其他页面
anyRequest().authenticated();
//登录页面 模拟客户端
httpSecurity.formLogin().loginPage("/home/login").permitAll();
//access deny
httpSecurity.exceptionHandling().accessDeniedHandler(userAccessDeniedHandler);
//unauthorized
httpSecurity.exceptionHandling().authenticationEntryPoint(userAuthenticationEntryPoint);
//验证请求是否正确
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}

2,UserAuthenticationEntryPoint.java

@Component
public class UserAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
System.out.println("i am 401");
ServletUtil.printRestResult(RestResult.error(ResponseCode.WEB_401));
}
}

说明:匿名用户访问无权限资源时的异常

3,UserAccessDeniedHandler.java

@Component("UserAccessDeniedHandler")
public class UserAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
System.out.println("UserAccessDeniedHandler");
ServletUtil.printRestResult(RestResult.error(ResponseCode.WEB_403));
}
}

说明:非匿名用户访问无权限访问的资源时的异常

4,SecUser.java

public class SecUser extends User {
//用户id
private int userid;
//昵称
private String nickname; public SecUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
} public SecUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
} public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
} public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
}

扩展spring security user类

5,JwtAuthticationFilter.java

@Component
public class JwtAuthticationFilter implements Filter { @Resource
private AuthenticationManager authenticationManager; @Autowired
private JwtTokenUtil jwtTokenUtil; @Autowired
private JwtUserDetailsService userDetailsService; @Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----------------AuthticationFilter init");
}
//过滤功能
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//得到当前的url
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String path = request.getServletPath();
if (path.equals("/auth/authenticate")) {
System.out.println("auth path:"+path);
//得到请求的post参数
String username = "";
String password = "";
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
StringBuffer sb=new StringBuffer();
String s=null;
while((s=br.readLine())!=null){
sb.append(s);
}
JSONObject jsonObject = JSONObject.parseObject(sb.toString());
username = jsonObject.getString("username");
password = jsonObject.getString("password");
//System.out.println("name:"+name+" age:"+age);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("username:"+username);
System.out.println("password:"+password);
String authResult = "";
try{
authResult = authenticate(username,password);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("authResult:"+authResult);
//验证通过后生成token返回
if ("success".equals(authResult)) {
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
final String token = jwtTokenUtil.generateToken(userDetails);
Map<String, String> mapData = new HashMap<String, String>();
mapData.put("token", token);
ServletUtil.printRestResult(RestResult.success(mapData)); } else if ("badcredential".equals(authResult)){
ServletUtil.printRestResult(RestResult.error(ResponseCode.LOGIN_FAIL));
} else {
ServletUtil.printRestResult(RestResult.error(ResponseCode.ERROR));
}
return;
} else {
System.out.println("not auth path:"+path);
filterChain.doFilter(servletRequest, servletResponse);
}
} @Override
public void destroy() {
System.out.println("----------------filter destroy");
} private String authenticate(String username, String password) throws Exception {
try {
System.out.println("username:"+username);
System.out.println("password:"+password);
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
System.out.println("authenticate:will return success");
return "success";
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
System.out.println("BadCredentialsException");
System.out.println(e.toString());
//throw new Exception("INVALID_CREDENTIALS", e);
return "badcredential";
}
}
}

用来实现登录的filter,验证通过后生成token返回

6,JwtRequestFilter.java

@Component
public class JwtRequestFilter extends OncePerRequestFilter { @Autowired
private JwtUserDetailsService jwtUserDetailsService; @Autowired
private JwtTokenUtil jwtTokenUtil; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token 获取请求头部的 Bearer
System.out.println("filter:header:"+requestTokenHeader);
//判断,从token中得到username
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
//System.out.println("filter :requestTokenHeader not null and start with bearer");
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
} catch (MalformedJwtException e) {
System.out.println("JWT Token MalformedJwtException");
}
} else {
//System.out.println("filter :requestTokenHeader is null || not start with bearer");
//logger.warn("JWT Token does not begin with Bearer String");
} // 验证,username,如果验证合法则保存到SecurityContextHolder
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//System.out.println("filter:username!=null");
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// JWT 验证通过 使用Spring Security 管理
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//System.out.println("usernamePasswordAuthenticationToken:"+usernamePasswordAuthenticationToken.toString());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
} else {
// System.out.println("jwtTokenUtil.validateToken not success");
}
}
chain.doFilter(request, response);
}
}

处理每次的请求,如果有token,则从token获取用户信息,验证用户信息合法,则把从数据库中得到的用户的相关信息保存到SecurityContextHolder

7,JwtUserDetailsService.java

@Service
public class JwtUserDetailsService implements UserDetailsService {
@Resource
private SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("-----loadUserByUsername");
SysUser oneUser = sysUserService.getOneUserByUsername(username);//数据库查询 看用户是否存在
String encodedPassword = oneUser.getPassword();
Collection<GrantedAuthority> collection = new ArrayList<>();//权限集合
//用户角色role前面要添加ROLE_
List<String> roles = oneUser.getRoles();
System.out.println(roles);
for (String roleone : roles) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+roleone);
collection.add(grantedAuthority);
}
//给用户增加用户id和昵称
SecUser user = new SecUser(username,encodedPassword,collection);
user.setUserid(oneUser.getUserId());
user.setNickname(oneUser.getNickName());
return user;
}
}

从数据库得到用户信息

8,JwtTokenUtil.java

@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; private String secret = "liuhongdi";
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
} public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//for retrieveing any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//generate token for user
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
//generate token
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
//validate token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}

处理JwtToken的工具类,用来生成token,验证token是否合法

9,login.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用 jwt 登录页面</title>
</head>
<body>
<div>
<input type="text" id="userName" name="userName" value="" placeholder="username">
</div>
<div>
<input type="password" id="password" name="password" value="" placeholder="password">
</div>
<div>
<input type="button" id="btnSave" onclick="go_login()" value="登录">
</div>
<script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
<script>
//登录
function go_login() {
var username=$("#userName").val();
var password=$("#password").val();
if ($("#userName").val() == "") {
alert('userName is empty');
$("#userName").focus();
return false;
}
if ($("#password").val() == "") {
alert('password is empty');
$("#password").focus();
return false;
}
var postData = {
"username":username ,
"password" : password
}
$.ajax({
cache: true,
type: "POST",
url: "/auth/authenticate",
contentType: "application/json;charset=UTF-8",
data:JSON.stringify(postData),
dataType: "json",
async: false,
error: function (request) {
console.log("Connection error");
},
success: function (data) {
//save token
console.log("data:");
console.log(data);
if (data.code == 0) {
//success
alert("success:"+data.msg+";token:"+data.data.token);
//save token
localStorage.setItem("token",data.data.token);
} else {
//failed
alert("failed:"+data.msg);
}
}
});
};
</script>
</body>
</html>

10,其他代码可从github上查看

五,测试效果

1,登录,访问:

http://127.0.0.1:8080/home/login

用admin登录:

可以看到返回的token

2,查看session信息:访问:

http://127.0.0.1:8080/home/getsession

点击:get session info

点击:get admin info:

可以正常访问

3,用merchant登录:

点击 get admin info:

提示拒绝访问

六,查看spring boot的版本:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.3.RELEASE)

spring boot:spring security整合jwt实现登录和权限验证(spring boot 2.3.3)的更多相关文章

  1. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  2. Spring Security整合JWT,实现单点登录,So Easy~!

    前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...

  3. 从零开始实现asp.net MVC4框架网站的用户登录以及权限验证模块 详细教程

    从零开始实现asp.net MVC4框架网站的用户登录以及权限验证模块 详细教程   用户登录与权限验证是网站不可缺少的一部分功能,asp.net MVC4框架内置了用于实现该功能的类库,只需要简单搭 ...

  4. Spring Security 整合JWT(四)

    一.前言 本篇文章将讲述Spring Security 简单整合JWT 处理认证授权 基本环境 spring-boot 2.1.8 mybatis-plus 2.2.0 mysql 数据库 maven ...

  5. Spring Boot 使用 JWT 进行身份和权限验证

    上周写了一个 适合初学者入门 Spring Security With JWT 的 Demo,这篇文章主要是对代码中涉及到的比较重要的知识点的说明. 适合初学者入门 Spring Security W ...

  6. spring boot:spring security用mysql数据库实现RBAC权限管理(spring boot 2.3.1)

    一,用数据库实现权限管理要注意哪些环节? 1,需要生成spring security中user类的派生类,用来保存用户id和昵称等信息, 避免页面上显示用户昵称时需要查数据库 2,如果需要在页面上显示 ...

  7. springboot security+redis+jwt+验证码 登录验证

    概述 基于jwt的token认证方案 验证码 框架的搭建,可以自己根据网上搭建,或者看我博客springboot相关的博客,这边就不做介绍了.验证码生成可以利用Java第三方组件,引入 <dep ...

  8. SpringBoot整合JWT实现登录认证

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点 ...

  9. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十二)Spring集成Redis缓存

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 整合Redis 本来以为类似的Redis教程和整合代码应该会很多,因 ...

随机推荐

  1. 高可用服务之Keepalived利用脚本实现服务的可用性检测

    上一篇博客主要聊到了keepalived高可用LVS集群的相关配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13659428.html:keepalive ...

  2. elo system

    今天了解了一下游戏中的PVP模块的实现,大多数的游戏都使用到了ELO算法,刚开始的时候并不清楚这个算法是做什么的,对此开始大量查找有关于ELO算法的资源,功夫不负有心人,总算找到一些有用的资源了. 先 ...

  3. range如何倒序

    for j in range(3,-2,-1): 表示对3进行每次加-1的操作,直到-2,但不包括-2 print(j) 打印出3 2 1 0 -1都换行展示的

  4. python中eval()

    eval()执行简单的python代码(个人感觉像是执行表达式)

  5. NSOperation类

    NSOperation 抽象类 NSOperation 是一个"抽象类",不能直接使用 抽象类的用处是定义子类共有的属性和方法 在苹果的头文件中,有些抽象类和子类的定义是在同一个头 ...

  6. css3 压缩及验证工具

    1.css w3c统一验证工具 网址:http://www.csstats.com/ 如果你想要更全面的,这个神奇,你值得拥有: w3c统一验证工具:http://validator.w3.org/u ...

  7. hystrix源码之插件

    HystrixPlugins 获取并发相关类(HystrixConcurrencyStrategy).事件通知类(HystrixEventNotifier).度量信息类(HystrixMetricsP ...

  8. pytest封神之路第五步 参数化进阶

    用过unittest的朋友,肯定知道可以借助DDT实现参数化.用过JMeter的朋友,肯定知道JMeter自带了4种参数化方式(见参考资料).pytest同样支持参数化,而且很简单很实用. 语法 在& ...

  9. nginx+tomcat集群方法

    下载地址:wget http://nginx.org/download/nginx-1.16.1.tar.gz 解压:tar -zxvf 预编译 nginx+tomcat集群方法: 进入nginx配置 ...

  10. 梯度下降法Gradient descent(最速下降法Steepest Descent)

    最陡下降法(steepest descent method)又称梯度下降法(英语:Gradient descent)是一个一阶最优化算法. 函数值下降最快的方向是什么?沿负梯度方向  d=−gk