狮山网站开发,软文是指什么,wordpress 阿里云主机,oa系统登录网址用 spring-boot 开发 RESTful API 非常的方便#xff0c;在生产环境中#xff0c;对发布的 API 增加授权保护是非常必要的。现在我们来看如何利用 JWT 技术为 API 增加授权保护#xff0c;保证只有获得授权的用户才能够访问 API。 一、Jwt 介绍 JSON Web Token (JWT)是一个开… 用 spring-boot 开发 RESTful API 非常的方便在生产环境中对发布的 API 增加授权保护是非常必要的。现在我们来看如何利用 JWT 技术为 API 增加授权保护保证只有获得授权的用户才能够访问 API。 一、Jwt 介绍 JSON Web Token (JWT)是一个开放标准(RFC 7519)它定义了一种紧凑的、自包含的方式 用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任因为它是数字签名的。 Jwt 主要应用场景授权 Authorization (授权) : 这是使用 JWT 的最常见场景。一旦用户登录后续每个请求都将包含 JWT允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的 JWT 的 一个特性因为它的开销很小并且可以轻松地跨域使用。 在认证的时候当用户用他们的凭证成功登录以后一个 JSON Web Token 将会被返回。此后token 就是用户凭证了。为什么不用 session因为做了完全的前后端分离前段页面每次发出 Ajax 请求都会建立一个新的回话请求没办法通过 session 来记录跟踪用户回话状态。所以采用 JWT来完成回话跟踪、身份验证。Session 是在服务器端的而 JWT 是在客户端的。
JWT 使用流程
用户携带用户名和密码请求访问服务器校验用户凭据应用提供一个 token 给客户端客户端存储 token并且在随后的每一次请求中都带着它服务器校验 token 并返回数据
二、搭建基础 SpringBoot 工程
2.1、新建一个 SpringBoot 工程引入所需依赖包
dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId
/dependency 2.2、编写测试 Controller HelloController package com.offcn.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
RestController
public class HelloController { RequestMapping(/hello) public String hello(){ return hello; }
}2.3、编写应用主启动类
package com.offcn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
SpringBootApplication
public class SpringbootSecurityJwtApplication {
public static void main(String[] args) { SpringApplication.run(SpringbootSecurityJwtApplication.class, args); }
}2.4、测试应用 访问地址http://localhost:8080/hello 至此我们的接口就开发完成了。但是这个接口没有任何授权防护任何人都可以访问这 样是不安全的下面我们开始加入授权机制。
三、增加用户注册功能
3.1、导入数据库所需依赖包
!-- spring-data-jpa --
dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId
/dependency
!-- mysql --
dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId
version5.1.30/version
/dependency
dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId
/dependency3.2、修改配置文件 application.yml 配置数据库连接
spring: datasource: url: jdbc:mysql://localhost:3306/springboot-security?serverTimezoneGMT%2B8 username: root password: 123 driver-class-name: com.mysql.jdbc.Driver jpa:hibernate: ddl-auto: update show-sql: true application: name: demo1 #配置应用名称 3.3、新建一个实体类 User Entity
Table(name tb_user)
Data
NoArgsConstructor
AllArgsConstructor
public class User {
Id
GeneratedValue private Long id; private String username; private String password;
}3.4、新建一个 dao 实现 JpaRepository 接口
package com.offcn.dao;
import com.offcn.po.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserDao extends JpaRepositoryUser,Long { User findByUsername(String username);
}3.5、新建 UserController 类中增加注册方法实现用户注册的接口 package com.offcn.controller;
import com.offcn.dao.UserDao;
import com.offcn.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
Controller
RequestMapping(/users)
public class UserController { Autowired private UserDao userDao; /*** 该方法是注册用户的方法默认放开访问控制 * param user */PostMapping(/signup) ResponseBody public String signUp(RequestBody User user) { user.setPassword(user.getPassword()); try {userDao.save(user); return success; } catch (Exception e) { e.printStackTrace(); return error;} }
}3.6 测试用户注册 请求地址http://localhost:8080/users/signup
四、添加 JWT 认证 用户填入用户名密码后与数据库里存储的用户信息进行比对如果通过则认证成功。传统的方法是在认证通过后创建 sesstion并给客户端返回 cookie。现在我们采用 JWT 来处理用户名密码的认证。区别在于认证通过后服务器生成一个 token将 token 返回给客户端客户端以后的所有请求都需要在 http 头中指定该 token。服务器接收的请求后会对 token 的合法性进行验证。验证的内容包括:内容是一个正确的 JWT 格式 、检查签名 、检查 claims 、检查权限。
4.1、导入 JWT 及 SpringSecurity 所需依赖包 dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId
/dependency
dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.7.0/version
/dependency 4.2、编写登录处理过滤器类 JWTLoginFilter 核心功能是在验证用户名密码正确后生成一个 token并将 token 返回给客户端 该类继承自 UsernamePasswordAuthenticationFilter重写了其中的 2 个方法
attemptAuthentication 接收并解析用户凭证。successfulAuthentication 用户成功登录后这个方法会被调用我们在这个方法里生成 token。
/**
* 验证用户名密码正确后生成一个 token并将 token 返回给客户端
*
该类继承自 UsernamePasswordAuthenticationFilter重写了其中的 2 个方法
*
attemptAuthentication 接收并解析用户凭证。
* successfulAuthentication 用户成功登录后这个方法会被调用我们在这个方法里生成
token。
*
*/
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter
{ private AuthenticationManager authenticationManager; public JWTLoginFilter(AuthenticationManager authenticationManager) { this.authenticationManager authenticationManager; }// 接收并解析用户凭证 Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try {User user new ObjectMapper() .readValue(req.getInputStream(), User.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( user.getUsername(), user.getPassword(), new ArrayList()) ); } catch (IOException e) { throw new RuntimeException(e); }
}
// 用户成功登录后这个方法会被调用我们在这个方法里生成 token
Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {String token Jwts.builder() .setSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername()) .setExpiration(new Date(System.currentTimeMillis() 60 * 60 * 24 * 1000)) .signWith(SignatureAlgorithm.HS512, MyJwtSecret) .compact(); res.addHeader(Authorization, Bearer token); }
}4.3、编写 Token 校验过滤器类 JWTAuthenticationFilter 用户一旦登录成功后会拿到 token后续的请求都会带着这个 token服务端会验证 token的合法性。该类继承自 BasicAuthenticationFilter在 doFilterInternal 方法中从 http 头的 Authorization 项读取 token 数据然后用 Jwts 包提供的方法校验 token 的合法性。如果校验通过就认为这是一个取得授权的合法请求。 /**
*
token 的校验
*
该类继承自 BasicAuthenticationFilter在 doFilterInternal 方法中
*
从 http 头的 Authorization 项读取 token 数据然后用 Jwts 包提供的方法校验 token 的合
法性。
* 如果校验通过就认为这是一个取得授权的合法请求
*
*/
public class JWTAuthenticationFilter extends BasicAuthenticationFilter { public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); }Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain
chain) throws IOException, ServletException { String header request.getHeader(Authorization);if (header null || !header.startsWith(Bearer )) { chain.doFilter(request, response); return; }UsernamePasswordAuthenticationToken authentication getAuthentication(request); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); }private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {String token request.getHeader(Authorization); if (token ! null) { // parse the token. String user Jwts.parser() .setSigningKey(MyJwtSecret) .parseClaimsJws(token.replace(Bearer , )) .getBody() .getSubject(); if (user ! null) { return new UsernamePasswordAuthenticationToken(user, null, new ArrayList()); }return null; }return null; }
}五、SpringSecurity 配置集成 JWT 认证
5.1、编写 SpringSecurity 用户及权限验证类UserDetailServiceImpl Service(userDetailServiceImpl)
public class UserDetailServiceImpl implements UserDetailsService {
Autowired
private UserDao userDao;
Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
//根据用户名查询用户信息
com.offcn.po.User user userDao.findByUsername(username);
//为用户授权暂时未验证权限
ListGrantedAuthority grantedAuthorityListnew ArrayList();
grantedAuthorityList.add(new SimpleGrantedAuthority(ROLE_USER));
return new User(username,user.getPassword(),grantedAuthorityList);
}
}5.2、修改程序主启动类增加密码加密生成器配置
SpringBootApplication
public class SpringbootSecurityJwtApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityJwtApplication.class, args);
}
Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}5.3、编写 SpringSecurity 配置类 WebSecurityConfig 通过 SpringSecurity 的配置将上面的 JWT 过滤器类组合在一起。 /**
*
SpringSecurity 的配置
*
通过 SpringSecurity 的配置将 JWTLoginFilterJWTAuthenticationFilter 组合在一起
*
*/
Configuration
Order(SecurityProperties.DEFAULT_FILTER_ORDER)
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
Autowired
private UserDetailsService
userDetailServiceImpl;
Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, /users/signup).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new
JWTLoginFilter(authenticationManager()))
.addFilter(new
JWTAuthenticationFilter(authenticationManager()));
}
Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailServiceImpl).passwordEncoder(bCryptPasswordEncod
er);
}
}5.4、修改 UserController 类 增加注册加密密码 Controller
RequestMapping(/users)
public class UserController {
Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
Autowired
private UserDao userDao;
/**
*
该方法是注册用户的方法默认放开访问控制
*
param user
*/
PostMapping(/signup)
ResponseBody
public String signUp(RequestBody User user) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
try {
userDao.save(user);
return success;
} catch (Exception e) {
e.printStackTrace();
return error;
} } }六、测试 Token
6.1、测试请求 hello 接口 请求地址http://localhost:8080/hello
6.2、重新注册一个账号 清空数据表 重新注册一个账号 请求地址http://localhost:8080/users/signup
查看数据库
6.3、测试登录 请求地址http://localhost:8080/login 发 post 请求
响应的请求头 Authorization 的值就是 token Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTY1MjY0OTQxfQ.CW-QwtE1Q2 Z69NNUnH_wPIaJjJpTFnh8eR3z03ujw-hb3aMO61yuir6w-T0X0FdV9k2WQrj903J9VDz6ijPJt Q 6.4、用登录后的 token 再次请求 hello 接口 注意在请求头中携带 token 请求头名称Authorization 携带对应的 token 值。 可以看到正常响应结果。