SpringSecurity前面讲解的一些配置都是基于前后端都是一起的,那么当分开了的时候就会出现一系列问题,跨域之类的问题随之而出 所以出现了jwt帮助我们完成SpringSecurity前后端分离权限控制
jwt (json web Token)它是基于RFC 7519开放标准用于双方安全展示信息的一种方式。通俗说就是是用于服务端和客户端相互交换信息的一种凭证。如果用过aouth2的小伙伴本对这个应该不陌生Token。
在传统模式当中我们的认证流程是
用户登录->服务端生成session->cookie ->服务端写代session->查找用户->findOK
1.服务端需一定资源保存session信息,用户多时资源消耗较大
扩展性不好,当我们的服务端需要集群时,
2.因session保存在服务端,此时无法定位session,造成登录失效
3.跨域问题,当我们访问A网站时,此时不想再登录就能够访问关联网站B。(传统解决办法:写入持久层,A,B同时访问)
所以现在可以采用的解决办法,不需要服务端去保存session,
用户登录--->服务端生成凭证->携带凭证带客户端——>客户端保存凭证->每次客户端请求都携代凭证,客户端通过则验证成功,调用API获取信息
。所以就有了jwt的诞生
jwt的组成部分
- header(头),保存算法,类型
- payload(负载),用户的信息,如id,用户名等等
- signature(签名),将生成的token编码(加密)
他们之间用 "."号隔开。
新建springboot项目
引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<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>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
编写yml文件
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/security_db?useSSL=false&serverTimezone=UTC
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
logging:
level:
org.springframework.*: debug
参考之前文章,编写实体和repository这里就不放代码了 直接放截图
image.png
因为是前后端分离的所有封装了一个专们响应前台数据的实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseDto<V> {
private int code;
private String msg;
private V data;
}
//编写securityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//添加异常
@Resource
private TokenExceptionHandler tokenExceptionHandler;
@Resource
private AccessDeniedHandler accessDeniedHandler;
@Resource
private JwtTokenFilter jwtTokenFilter;
//获取Token的接口不必拦截
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers( HttpMethod.GET,"/token" );
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//前后端分离所以不要考虑csrf可以禁用掉
http.csrf().disable()
//添加异常处理
.exceptionHandling().authenticationEntryPoint( tokenExceptionHandler )
.accessDeniedHandler( accessDeniedHandler )
//
.and().sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS )
//拦截所有请求
.and().authorizeRequests().anyRequest().authenticated();
//定义filter addFilterAt 用于filter替换
http.addFilterAt(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
//super.configure( http );
}
}
//对没有Token 的请求进行拦截 编写handler处理
//验证没有token异常
@Component
public class TokenExceptionHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 直接返回 json错误
ResponseDto <Object> result = new ResponseDto<>();
//20,标识没有token
result.setCode(20);
result.setMsg("请求无效,没有有效token");
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}
//访问被拒绝处理
@Component
public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 返回我们的自定义json
ObjectMapper objectMapper = new ObjectMapper();
ResponseDto <Object> result = new ResponseDto<>();
//50,标识有token,但是该用户没有权限
result.setCode(50);
result.setMsg("请求无效,没有有效token");
response.getWriter().write(objectMapper.writeValueAsString(result)); // 返回我们的自定义json
}
}
//自定义Filter
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
String token = request.getHeader("token");
//获取token,并且解析token,如果解析成功,则放入 SecurityContext
if (token != null) {
try {
AuthUser authUser = JwtUtil.parseToken(token);
//todo: 如果此处不放心解析出来的 authuser,可以再从数据库查一次,验证用户身份:
//解析成功
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//我们依然使用原来filter中的token对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch (Exception e) {
logger.info("解析失败,可能是伪造的或者该token已经失效了(我们设置失效5分钟)。");
}
}
filterChain.doFilter(request, response);
}
}
controller代码
@RestController
public class UserController {
@Resource
private UserRepository userRepository;
@Resource
private RoleRepository roleRepository;
@GetMapping("/token")
public ResponseDto login(String username, String password) {
User user = userRepository.findByUsername(username);
if (user == null || !user.getPassword().equals(password)) {
ResponseDto <Object> result = new ResponseDto <>();
result.setCode(10);
result.setMsg("用户名或密码错误");
return result;
}
ResponseDto <Object> success = new ResponseDto <>();
//用户名密码正确,生成token给客户端
success.setCode(0);
List <Role> roles = Collections.singletonList(roleRepository.findById(user.getId()).get());
success.setData( JwtUtil.generateToken(username, roles));
return success;
}
}
@RestController
@RequestMapping
public class PermissionController {
@GetMapping("/permission")
public ResponseDto loginTest(@AuthenticationPrincipal AuthUser authUser) {
ResponseDto<String> resultVO = new ResponseDto<>();
resultVO.setCode(0);
resultVO.setData("你成功访问了该api,这代表你已经登录,你是: " + authUser);
return resultVO;
}
@GetMapping("/loginUser")
@PreAuthorize("hasRole('user')")
public ResponseDto loginTest() {
ResponseDto<String> resultVO = new ResponseDto<>();
resultVO.setCode(0);
resultVO.setData("你成功访问了需要有 user 角色的api。");
return resultVO;
}
}
采用postman进行测试 直接放图
image.png image.png
带入token在header里面去访问 不需要角色的接口
image.png
image.png
新建一个没有该角色的用户去访问
image.png
总结:jwt+security整合流程
- 引入jwt security的pom文件
- 创建实体 实现userDetail 和security交互的实体类
- 自定义需要处理的异常 访问被拒绝的异常等、看个人所需
4.编写filter 自定义filter 去验证token是否通过 ,以及保证失效token不会进入接口 - 编写securityconfig 配置websecurity 让获取token接口绕过security 编写httpsecurity设置设置对web端所有的都进行拦截 都需要经过验证才行,添加异常处理
添加对filter替换。替换掉UsernamePasswordAuthenticationFilter 该filter默认情况响应的是/login
网友评论