国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > 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教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 尼木县| 年辖:市辖区| 禹州市| 丽江市| 忻州市| 平陆县| 庐江县| 大洼县| 紫金县| 新泰市| 翼城县| 濉溪县| 信丰县| 静宁县| 武平县| 天峨县| 阿图什市| 怀宁县| 武清区| 宜宾市| 延吉市| 永福县| 宜春市| 林周县| 葵青区| 望都县| 济南市| 九龙县| 肥西县| 云和县| 冷水江市| 陕西省| 曲水县| 长沙县| 义乌市| 温宿县| 赞皇县| 石屏县| 西青区| 申扎县| 黎平县|