在先后端分离的,后端微服务的状况下,会话已经再也不适合保存在服务端,使用redis能够保存会话,可是须要在redis集群之间进行复制,若是用户较多,保存的数据量也比较大。前端
JWT实现无状态的会话机制,是一个解决方案。java
微服务集群中的每一个服务,对外提供的都使用RESTful风格的接口。而RESTful风格的一个最重要的规范就是:服务的无状态性,即:git
一、服务端不保存任何客户端请求者信息
二、客户端的每次请求必须具有自描述信息,经过这些信息识别客户端身份github
一、首先客户端发送帐户名/密码到服务端进行认证
二、认证经过后,服务端将用户信息加密而且编码成一个token,返回给客户端
三、之后客户端每次发送请求,都须要携带认证的token
四、服务端对客户端发送来的token进行解密,判断是否有效,而且获取用户登陆信息web
JWT 做为一种规范,并无和某一种语言绑定在一块儿,经常使用的Java 实现是GitHub 上的开源项目 jjwt,地址以下:https://github.com/jwtk/jjwtredis
说明:spring
一、一个登录接口/login,用于获取token
二、一个用户接口/hello,用户角色能够访问
三、一个管理接口/admin,管理角色能够访问数据库
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); } } }
<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>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> <version>5.1.3.RELEASE</version> </dependency>
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<GrantedAuthority> 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; } }
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 !"; } }
一、一个是用户登陆的过滤器,在用户的登陆的过滤器中校验用户是否登陆成功,若是登陆成功,则生成一个token返回给客户端,登陆失败则给前端一个登陆失败的提示。
二、第二个过滤器则是当其余请求发送来,校验token的过滤器,若是校验成功,就让请求继续执行。json
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<GrantedAuthority> 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(); } }
一、简单起见,这里并未链接数据库,我直接在内存中配置了两个用户,两个用户具有不一样的角色。
二、配置路径规则时, /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(); } }
一、获取token
二、带令牌访问