php如何实现无状态,springCloud-依赖Spring Security使用 JWT实现无状态的分布式会话...

案例

在前后端分离的,后端微服务的情况下,会话已经不再适合保存在服务端,使用redis可以保存会话,但是需要在redis集群之间进行复制,如果用户较多,保存的数据量也比较大。

JWT实现无状态的会话机制,是一个解决方案。

1、什么是无状态?

微服务集群中的每个服务,对外提供的都使用RESTful风格的接口。而RESTful风格的一个最重要的规范就是:服务的无状态性,即:

1、服务端不保存任何客户端请求者信息

2、客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

2、如何实现无状态?

1、首先客户端发送账户名/密码到服务端进行认证

2、认证通过后,服务端将用户信息加密并且编码成一个token,返回给客户端

3、以后客户端每次发送请求,都需要携带认证的token

4、服务端对客户端发送来的token进行解密,判断是否有效,并且获取用户登录信息

3、JWT

JWT 作为一种规范,并没有和某一种语言绑定在一起,常用的Java 实现是GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjwt

代码

说明:

1、一个登陆接口/login,用于获取token

2、一个用户接口/hello,用户角色可以访问

3、一个管理接口/admin,管理角色可以访问

1、启动文件

package com.baiziwan.authorize;

import org.mybatis.spring.annotation.MapperScan;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication

@EnableDiscoveryClient

@MapperScan({"com.baiziwan.authorize.*.dao", "com.baiziwan.authorize.*.*.dao"})

@EnableAsync

public class AuthorizeApplication {

private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizeApplication.class);

public static void main(String[] args) {

try {

SpringApplication.run(AuthorizeApplication.class, args);

} catch (Exception e) {

LOGGER.error("启动失败!", e);

}

}

}

2、pom文件主要依赖

org.springframework.boot

spring-boot-starter-security

org.springframework.boot

spring-boot-starter-web

io.jsonwebtoken

jjwt

0.9.1

org.springframework.security

spring-security-oauth2-resource-server

5.1.3.RELEASE

3、User文件

package com.baiziwan.authorize.model;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

import java.util.List;

public class User implements UserDetails {

private String username;

private String password;

private List authorities;

@Override

public Collection extends GrantedAuthority> getAuthorities() {

return authorities;

}

@Override

public String getPassword() {

return password;

}

public String getUsername() {

return username;

}

@Override

public boolean isAccountNonExpired() {

return true;

}

@Override

public boolean isAccountNonLocked() {

return true;

}

@Override

public boolean isCredentialsNonExpired() {

return true;

}

@Override

public boolean isEnabled() {

return true;

}

}

4、控制器url

package com.baiziwan.authorize.controller;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

public class HelloController {

//需要jwt访问,user角色可以访问

@GetMapping("/hello")

public String hello() {

return "hello jwt !";

}

//需要jwt访问,admin角色可以访问

@GetMapping("/admin")

public String admin() {

return "hello admin !";

}

}

5、JWT 过滤器配置

1、一个是用户登录的过滤器,在用户的登录的过滤器中校验用户是否登录成功,如果登录成功,则生成一个token返回给客户端,登录失败则给前端一个登录失败的提示。

2、第二个过滤器则是当其他请求发送来,校验token的过滤器,如果校验成功,就让请求继续执行。

package com.baiziwan.authorize.filter;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.AuthorityUtils;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

import java.util.List;

public class JwtFilter extends GenericFilterBean {

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) servletRequest;

String jwtToken = req.getHeader("authorization");

System.out.println(jwtToken);

Claims claims = Jwts.parser().setSigningKey("sang@123").parseClaimsJws(jwtToken.replace("Bearer",""))

.getBody();

String username = claims.getSubject();//获取当前登录用户名

List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities);

SecurityContextHolder.getContext().setAuthentication(token);

filterChain.doFilter(req,servletResponse);

}

}

package com.baiziwan.authorize.filter;

import com.baiziwan.authorize.model.User;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

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.io.PrintWriter;

import java.util.Collection;

import java.util.Date;

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {

public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {

super(new AntPathRequestMatcher(defaultFilterProcessesUrl));

setAuthenticationManager(authenticationManager);

}

@Override

public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException {

User user = new ObjectMapper().readValue(req.getInputStream(), User.class);

return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));

}

@Override

protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse resp, FilterChain chain, Authentication authResult) throws IOException, ServletException {

Collection extends GrantedAuthority> authorities = authResult.getAuthorities();

StringBuffer as = new StringBuffer();

for (GrantedAuthority authority : authorities) {

as.append(authority.getAuthority())

.append(",");

}

String jwt = Jwts.builder()

.claim("authorities", as)//配置用户角色

.setSubject(authResult.getName())

.setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000))

.signWith(SignatureAlgorithm.HS512,"sang@123")

.compact();

resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter();

out.write(new ObjectMapper().writeValueAsString(jwt));

out.flush();

out.close();

}

protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse resp, AuthenticationException failed) throws IOException, ServletException {

resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter();

out.write("登录失败!");

out.flush();

out.close();

}

}

6、Spring Security 配置

1、简单起见,这里并未连接数据库,我直接在内存中配置了两个用户,两个用户具备不同的角色。

2、配置路径规则时, /hello 接口必须要具备 user 角色才能访问, /admin 接口必须要具备 admin 角色才能访问,POST 请求并且是 /login 接口则可以直接通过,其他接口必须认证后才能访问。

package com.baiziwan.authorize.config;

import com.baiziwan.authorize.filter.JwtFilter;

import com.baiziwan.authorize.filter.JwtLoginFilter;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.HttpMethod;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.password.NoOpPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean

PasswordEncoder passwordEncoder() {

return NoOpPasswordEncoder.getInstance();

}

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.inMemoryAuthentication().withUser("admin")

.password("123").roles("admin")

.and()

.withUser("sang")

.password("456")

.roles("user");

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.antMatchers(HttpMethod.GET,"/.well-known/jwks.json").permitAll()

.antMatchers("/hello").hasRole("user")

.antMatchers("/admin").hasRole("admin")

.antMatchers(HttpMethod.POST, "/login").permitAll()

.anyRequest().authenticated()

.and()

.addFilterBefore(new JwtLoginFilter("/login",authenticationManager()),UsernamePasswordAuthenticationFilter.class)

.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)

.csrf().disable();

}

}

7、测试效果

1、获取token

0b009794eb5fc8e0b8cde74ac41a3e9b.png

2、带令牌访问

2bb93dd2da64c384caf6111c9de0406c.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值