热门IT资讯网

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

发表于:2024-11-27 作者:热门IT资讯网编辑
编辑最后更新 2024年11月27日,案例在前后端分离的,后端微服务的情况下,会话已经不再适合保存在服务端,使用redis可以保存会话,但是需要在redis集群之间进行复制,如果用户较多,保存的数据量也比较大。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"})@EnableAsyncpublic 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-oauth3-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 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;@RestControllerpublic 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 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;@Configurationpublic 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

2、带令牌访问

0