亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Spring Security結合JWT的方法教程

2024-07-13 10:14:38
字體:
來源:轉載
供稿:網友

概述

眾所周知使用 JWT 做權限驗證,相比 Session 的優點是,Session 需要占用大量服務器內存,并且在多服務器時就會涉及到共享 Session 問題,在手機等移動端訪問時比較麻煩

而 JWT 無需存儲在服務器,不占用服務器資源(也就是無狀態的),用戶在登錄后拿到 Token 后,訪問需要權限的請求時附上 Token(一般設置在Http請求頭),JWT 不存在多服務器共享的問題,也沒有手機移動端訪問問題,若為了提高安全,可將 Token 與用戶的 IP 地址綁定起來

前端流程

用戶通過 AJAX 進行登錄得到一個 Token

之后訪問需要權限請求時附上 Token 進行訪問

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script type="application/javascript">  var header = "";  function login() {   $.post("http://localhost:8080/auth/login", {    username: $("#username").val(),    password: $("#password").val()   }, function (data) {    console.log(data);    header = data;   })  }  function toUserPageBtn() {   $.ajax({    type: "get",    url: "http://localhost:8080/userpage",    beforeSend: function (request) {     request.setRequestHeader("Authorization", header);    },    success: function (data) {     console.log(data);    }   });  } </script></head><body> <fieldset>  <legend>Please Login</legend>  <label>UserName</label><input type="text" id="username">  <label>Password</label><input type="text" id="password">  <input type="button" onclick="login()" value="Login"> </fieldset> <button id="toUserPageBtn" onclick="toUserPageBtn()">訪問UserPage</button></body></html>

后端流程(Spring Boot + Spring Security + JJWT)

思路:

  • 創建用戶、權限實體類與數據傳輸對象
  • 編寫 Dao 層接口,用于獲取用戶信息
  • 實現 UserDetails(Security 支持的用戶實體對象,包含權限信息)
  • 實現 UserDetailsSevice(從數據庫中獲取用戶信息,并包裝成UserDetails)
  • 編寫 JWTToken 生成工具,用于生成、驗證、解析 Token
  • 配置 Security,配置請求處理 與 設置 UserDetails 獲取方式為自定義的 UserDetailsSevice
  • 編寫 LoginController,接收用戶登錄名密碼并進行驗證,若驗證成功返回 Token 給用戶
  • 編寫過濾器,若用戶請求頭或參數中包含 Token 則解析,并生成 Authentication,綁定到 SecurityContext ,供 Security 使用
  • 用戶訪問了需要權限的頁面,卻沒附上正確的 Token,在過濾器處理時則沒有生成 Authentication,也就不存在訪問權限,則無法訪問,否之訪問成功

編寫用戶實體類,并插入一條數據

User(用戶)實體類

@Data@Entitypublic class User { @Id @GeneratedValue private int id; private String name; private String password; @ManyToMany(cascade = {CascadeType.REFRESH}, fetch = FetchType.EAGER) @JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "uid", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "rid", referencedColumnName = "id")}) private List<Role> roles;} 

Role(權限)實體類

@Data@Entitypublic class Role { @Id @GeneratedValue private int id; private String name; @ManyToMany(mappedBy = "roles") private List<User> users;}

插入數據

User 表

 

id name password
1 linyuan 123

 

Role 表

 

id name
1 USER

 

User_ROLE 表

 

uid rid
1 1

 

Dao 層接口,通過用戶名獲取數據,返回值為 Java8 的 Optional 對象

public interface UserRepository extends Repository<User,Integer> { Optional<User> findByName(String name);}

編寫 LoginDTO,用于與前端之間數據傳輸

@Datapublic class LoginDTO implements Serializable { @NotBlank(message = "用戶名不能為空") private String username; @NotBlank(message = "密碼不能為空") private String password;}

編寫 Token 生成工具,利用 JJWT 庫創建,一共三個方法:生成 Token(返回String)、解析 Token(返回Authentication認證對象)、驗證 Token(返回布爾值)

@Componentpublic class JWTTokenUtils { private final Logger log = LoggerFactory.getLogger(JWTTokenUtils.class); private static final String AUTHORITIES_KEY = "auth"; private String secretKey;   //簽名密鑰 private long tokenValidityInMilliseconds;  //失效日期 private long tokenValidityInMillisecondsForRememberMe;  //(記住我)失效日期 @PostConstruct public void init() {  this.secretKey = "Linyuanmima";  int secondIn1day = 1000 * 60 * 60 * 24;  this.tokenValidityInMilliseconds = secondIn1day * 2L;  this.tokenValidityInMillisecondsForRememberMe = secondIn1day * 7L; } private final static long EXPIRATIONTIME = 432_000_000; //創建Token public String createToken(Authentication authentication, Boolean rememberMe){  String authorities = authentication.getAuthorities().stream()  //獲取用戶的權限字符串,如 USER,ADMIN    .map(GrantedAuthority::getAuthority)    .collect(Collectors.joining(","));  long now = (new Date()).getTime();    //獲取當前時間戳  Date validity;           //存放過期時間  if (rememberMe){   validity = new Date(now + this.tokenValidityInMilliseconds);  }else {   validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);  }  return Jwts.builder()         //創建Token令牌    .setSubject(authentication.getName())   //設置面向用戶    .claim(AUTHORITIES_KEY,authorities)    //添加權限屬性    .setExpiration(validity)      //設置失效時間    .signWith(SignatureAlgorithm.HS512,secretKey) //生成簽名    .compact(); } //獲取用戶權限 public Authentication getAuthentication(String token){  System.out.println("token:"+token);  Claims claims = Jwts.parser()       //解析Token的payload    .setSigningKey(secretKey)    .parseClaimsJws(token)    .getBody();  Collection<? extends GrantedAuthority> authorities =    Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))   //獲取用戶權限字符串    .map(SimpleGrantedAuthority::new)    .collect(Collectors.toList());             //將元素轉換為GrantedAuthority接口集合  User principal = new User(claims.getSubject(), "", authorities);  return new UsernamePasswordAuthenticationToken(principal, "", authorities); } //驗證Token是否正確 public boolean validateToken(String token){  try {   Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); //通過密鑰驗證Token   return true;  }catch (SignatureException e) {          //簽名異常   log.info("Invalid JWT signature.");   log.trace("Invalid JWT signature trace: {}", e);  } catch (MalformedJwtException e) {         //JWT格式錯誤   log.info("Invalid JWT token.");   log.trace("Invalid JWT token trace: {}", e);  } catch (ExpiredJwtException e) {         //JWT過期   log.info("Expired JWT token.");   log.trace("Expired JWT token trace: {}", e);  } catch (UnsupportedJwtException e) {        //不支持該JWT   log.info("Unsupported JWT token.");   log.trace("Unsupported JWT token trace: {}", e);  } catch (IllegalArgumentException e) {        //參數錯誤異常   log.info("JWT token compact of handler are invalid.");   log.trace("JWT token compact of handler are invalid trace: {}", e);  }  return false; }}

實現 UserDetails 接口,代表用戶實體類,在我們的 User 對象上在進行包裝,包含了權限等性質,可以供 Spring Security 使用

public class MyUserDetails implements UserDetails{ private User user; public MyUserDetails(User user) {  this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() {  List<Role> roles = user.getRoles();  List<GrantedAuthority> authorities = new ArrayList<>();  StringBuilder sb = new StringBuilder();  if (roles.size()>=1){   for (Role role : roles){    authorities.add(new SimpleGrantedAuthority(role.getName()));   }   return authorities;  }  return AuthorityUtils.commaSeparatedStringToAuthorityList(""); } @Override public String getPassword() {  return user.getPassword(); } @Override public String getUsername() {  return user.getName(); } @Override public boolean isAccountNonExpired() {  return true; } @Override public boolean isAccountNonLocked() {  return true; } @Override public boolean isCredentialsNonExpired() {  return true; } @Override public boolean isEnabled() {  return true; }}

實現 UserDetailsService 接口,該接口僅有一個方法,用來獲取 UserDetails,我們可以從數據庫中獲取 User 對象,然后將其包裝成 UserDetails 并返回

@Servicepublic class MyUserDetailsService implements UserDetailsService { @Autowired UserRepository userRepository; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {  //從數據庫中加載用戶對象  Optional<User> user = userRepository.findByName(s);  //調試用,如果值存在則輸出下用戶名與密碼  user.ifPresent((value)->System.out.println("用戶名:"+value.getName()+" 用戶密碼:"+value.getPassword()));  //若值不再則返回null  return new MyUserDetails(user.orElse(null)); }}

編寫過濾器,用戶如果攜帶 Token 則獲取 Token,并根據 Token 生成 Authentication 認證對象,并存放到 SecurityContext 中,供 Spring Security 進行權限控制

public class JwtAuthenticationTokenFilter extends GenericFilterBean { private final Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Autowired private JWTTokenUtils tokenProvider; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  System.out.println("JwtAuthenticationTokenFilter");  try {   HttpServletRequest httpReq = (HttpServletRequest) servletRequest;   String jwt = resolveToken(httpReq);   if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {   //驗證JWT是否正確    Authentication authentication = this.tokenProvider.getAuthentication(jwt);  //獲取用戶認證信息    SecurityContextHolder.getContext().setAuthentication(authentication);   //將用戶保存到SecurityContext   }   filterChain.doFilter(servletRequest, servletResponse);  }catch (ExpiredJwtException e){          //JWT失效   log.info("Security exception for user {} - {}",     e.getClaims().getSubject(), e.getMessage());   log.trace("Security exception trace: {}", e);   ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);  } } private String resolveToken(HttpServletRequest request){  String bearerToken = request.getHeader(WebSecurityConfig.AUTHORIZATION_HEADER);   //從HTTP頭部獲取TOKEN  if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){   return bearerToken.substring(7, bearerToken.length());        //返回Token字符串,去除Bearer  }  String jwt = request.getParameter(WebSecurityConfig.AUTHORIZATION_TOKEN);    //從請求參數中獲取TOKEN  if (StringUtils.hasText(jwt)) {   return jwt;  }  return null; }}

編寫 LoginController,用戶通過用戶名、密碼訪問 /auth/login,通過 LoginDTO 對象接收,創建一個 Authentication 對象,代碼中為 UsernamePasswordAuthenticationToken,判斷對象是否存在,通過 AuthenticationManager 的 authenticate 方法對認證對象進行驗證,AuthenticationManager 的實現類 ProviderManager 會通過 AuthentionProvider(認證處理) 進行驗證,默認 ProviderManager 調用 DaoAuthenticationProvider 進行認證處理,DaoAuthenticationProvider 中會通過 UserDetailsService(認證信息來源) 獲取 UserDetails ,若認證成功則返回一個包含權限的 Authention,然后通過 SecurityContextHolder.getContext().setAuthentication() 設置到 SecurityContext 中,根據 Authentication 生成 Token,并返回給用戶

@RestControllerpublic class LoginController { @Autowired private UserRepository userRepository; @Autowired private AuthenticationManager authenticationManager; @Autowired private JWTTokenUtils jwtTokenUtils; @RequestMapping(value = "/auth/login",method = RequestMethod.POST) public String login(@Valid LoginDTO loginDTO, HttpServletResponse httpResponse) throws Exception{  //通過用戶名和密碼創建一個 Authentication 認證對象,實現類為 UsernamePasswordAuthenticationToken  UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(),loginDTO.getPassword());  //如果認證對象不為空  if (Objects.nonNull(authenticationToken)){   userRepository.findByName(authenticationToken.getPrincipal().toString())     .orElseThrow(()->new Exception("用戶不存在"));  }  try {   //通過 AuthenticationManager(默認實現為ProviderManager)的authenticate方法驗證 Authentication 對象   Authentication authentication = authenticationManager.authenticate(authenticationToken);   //將 Authentication 綁定到 SecurityContext   SecurityContextHolder.getContext().setAuthentication(authentication);   //生成Token   String token = jwtTokenUtils.createToken(authentication,false);   //將Token寫入到Http頭部   httpResponse.addHeader(WebSecurityConfig.AUTHORIZATION_HEADER,"Bearer "+token);   return "Bearer "+token;  }catch (BadCredentialsException authentication){   throw new Exception("密碼錯誤");  } }}

編寫 Security 配置類,繼承 WebSecurityConfigurerAdapter,重寫 configure 方法

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_TOKEN = "access_token"; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {  auth    //自定義獲取用戶信息    .userDetailsService(userDetailsService)    //設置密碼加密    .passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception {  //配置請求訪問策略  http    //關閉CSRF、CORS    .cors().disable()    .csrf().disable()    //由于使用Token,所以不需要Session    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)    .and()    //驗證Http請求    .authorizeRequests()    //允許所有用戶訪問首頁 與 登錄    .antMatchers("/","/auth/login").permitAll()    //其它任何請求都要經過認證通過    .anyRequest().authenticated()    //用戶頁面需要用戶權限    .antMatchers("/userpage").hasAnyRole("USER")    .and()    //設置登出    .logout().permitAll();  //添加JWT filter 在  http    .addFilterBefore(genericFilterBean(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() {  return new BCryptPasswordEncoder(); } @Bean public GenericFilterBean genericFilterBean() {  return new JwtAuthenticationTokenFilter(); }}

編寫用于測試的Controller

@RestControllerpublic class UserController { @PostMapping("/login") public String login() {  return "login"; } @GetMapping("/") public String index() {  return "hello"; } @GetMapping("/userpage") public String httpApi() {  System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());  return "userpage"; } @GetMapping("/adminpage") public String httpSuite() {  return "userpage"; }}

案例源碼下載

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91av视频在线免费观看| 欧美特黄级在线| 精品国产999| 日韩av综合网站| 精品久久久久久中文字幕一区奶水| 久久精品电影一区二区| 国产日韩欧美91| 国产美女精品免费电影| 亚洲成人亚洲激情| 欧美日韩色婷婷| 久久人人爽亚洲精品天堂| 久久的精品视频| 欧美视频第一页| 韩剧1988在线观看免费完整版| 91精品久久久久久久久久久久久久| 国产成人亚洲综合青青| 亚洲第一区在线观看| 日韩av电影免费观看高清| 亚洲一二三在线| 国产精品自产拍在线观看中文| 影音先锋欧美在线资源| 色狠狠久久aa北条麻妃| 精品亚洲国产成av人片传媒| 北条麻妃一区二区三区中文字幕| 久久天天躁狠狠躁老女人| 亚洲欧美日韩网| 欧美一级电影在线| 国产成人精品免高潮在线观看| 青草热久免费精品视频| 亚洲网站在线观看| 色琪琪综合男人的天堂aⅴ视频| 在线电影av不卡网址| 国产日韩精品在线| 国产精品国内视频| 久久精品青青大伊人av| 国产精品久久久久久一区二区| 成人午夜在线视频一区| 欧美黑人巨大xxx极品| 精品久久久久久国产| 中日韩美女免费视频网站在线观看| 国产精品久久久久久久久粉嫩av| 欧美福利小视频| 国产小视频国产精品| 亚洲精品中文字| 国产精品精品视频一区二区三区| 精品久久久久久久久久久久| 亚洲剧情一区二区| 97在线精品国自产拍中文| 国产69精品99久久久久久宅男| 九九视频直播综合网| 成人在线中文字幕| 亚洲综合社区网| 亚洲高清在线观看| 久久综合伊人77777尤物| 国产97在线播放| 自拍偷拍亚洲精品| 日韩欧美视频一区二区三区| 91久久久久久| 91九色视频导航| 日韩黄色高清视频| 午夜精品一区二区三区视频免费看| 91国产美女在线观看| 亲子乱一区二区三区电影| 欧美国产中文字幕| 俺去亚洲欧洲欧美日韩| 亚洲一区二区三区毛片| 久久天天躁狠狠躁夜夜av| 亚洲二区在线播放视频| 91精品国产免费久久久久久| 青青久久aⅴ北条麻妃| 日韩亚洲精品视频| 韩国精品美女www爽爽爽视频| 欧美中文字幕在线播放| 欧美成人性生活| www.美女亚洲精品| 成人综合网网址| 久久九九国产精品怡红院| 亚洲综合社区网| 亚洲精品在线观看www| 久久夜色精品国产| 久久人人爽人人爽人人片av高请| 欧美富婆性猛交| 日韩欧美黄色动漫| 久久精品视频亚洲| 92福利视频午夜1000合集在线观看| 亚洲欧美日韩综合| 日韩精品在线观| 国产成人+综合亚洲+天堂| 国产亚洲人成网站在线观看| 亚洲最大中文字幕| 超薄丝袜一区二区| 日韩av中文字幕在线播放| 欧美成人亚洲成人| 国产精品视频地址| 国模叶桐国产精品一区| 亚洲三级 欧美三级| 国产精品久久久久av免费| 欧美精品在线播放| 久久综合色88| 午夜精品理论片| 成人激情在线播放| 久久天天躁狠狠躁夜夜躁| 91沈先生在线观看| 91欧美精品成人综合在线观看| 日韩电影在线观看中文字幕| 欧美日韩在线观看视频小说| 亚洲美女中文字幕| 亚洲人精品午夜在线观看| 亚洲影院色在线观看免费| 丝袜亚洲另类欧美重口| 欧美交受高潮1| 亚洲精品日韩在线| 亚洲人成77777在线观看网| 97视频在线观看视频免费视频| 亚洲精品电影网在线观看| 亚洲成人精品久久| 亚洲美女在线看| 欧美理论电影在线观看| 成人免费网站在线看| 午夜精品视频网站| 日韩成人中文电影| 日韩电影在线观看永久视频免费网站| 国产欧美日韩精品专区| 亚洲丁香久久久| 亚洲国产成人爱av在线播放| 91精品国产高清久久久久久久久| 在线播放亚洲激情| 久久在精品线影院精品国产| 欧美成人剧情片在线观看| 亚洲永久在线观看| 一区二区三区久久精品| 高清亚洲成在人网站天堂| 国产精品999| 2019中文字幕在线免费观看| 欧美精品日韩www.p站| 在线不卡国产精品| 日韩美女在线看| 夜夜嗨av色一区二区不卡| 一本色道久久综合狠狠躁篇怎么玩| 裸体女人亚洲精品一区| 国产欧美亚洲精品| 91欧美精品成人综合在线观看| 国产精品va在线播放我和闺蜜| 日韩高清免费观看| 欧洲成人免费aa| 欧美精品第一页在线播放| 亚洲欧洲xxxx| 日韩在线中文字幕| 亚洲欧美国产一区二区三区| 中文字幕欧美精品日韩中文字幕| 日韩欧美视频一区二区三区| 欧美激情亚洲视频| 2019精品视频| 国产精品成熟老女人| 欧美日韩美女在线| 尤物九九久久国产精品的分类| 亚洲免费中文字幕| 91精品在线观看视频| 久久久久久久av| 55夜色66夜色国产精品视频| 久久精品男人天堂| 成人久久精品视频| 国产欧美久久一区二区| 精品亚洲国产视频|