您好,登錄后才能下訂單哦!
在前后端分離的,后端微服務的情況下,會話已經不再適合保存在服務端,使用redis可以保存會話,但是需要在redis集群之間進行復制,如果用戶較多,保存的數據量也比較大。
JWT實現無狀態的會話機制,是一個解決方案。
微服務集群中的每個服務,對外提供的都使用RESTful風格的接口。而RESTful風格的一個最重要的規范就是:服務的無狀態性,即:
1、服務端不保存任何客戶端請求者信息
2、客戶端的每次請求必須具備自描述信息,通過這些信息識別客戶端身份
1、首先客戶端發送賬戶名/密碼到服務端進行認證
2、認證通過后,服務端將用戶信息加密并且編碼成一個token,返回給客戶端
3、以后客戶端每次發送請求,都需要攜帶認證的token
4、服務端對客戶端發送來的token進行解密,判斷是否有效,并且獲取用戶登錄信息
JWT 作為一種規范,并沒有和某一種語言綁定在一起,常用的Java 實現是GitHub 上的開源項目 jjwt,地址如下:https://github.com/jwtk/jjwt
說明:
1、一個登陸接口/login,用于獲取token
2、一個用戶接口/hello,用戶角色可以訪問
3、一個管理接口/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-oauth3-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 !";
}
}
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<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();
}
}
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();
}
}
1、獲取token
2、帶令牌訪問
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。