日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用spring security设置用户jwt令牌和设置接口访问权限案例

發布時間:2024/9/30 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用spring security设置用户jwt令牌和设置接口访问权限案例 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 1.配置Swagger
  • 2.spring security配置
  • 3.用戶校驗邏輯
    • 注冊和登錄接口
  • dao層 service層 pojo層
  • 4.加密驗證邏輯
  • 5.生成令牌邏輯
    • 身份驗證提供者:
    • 自定義令牌對象:
  • 6.登錄認證過濾器:
  • 7.JwtToken工具類:
    • security工具類
  • 8.權限封裝:
  • 9.用戶模型:
  • 10.測試生成令牌
  • 11.測試用戶權限


1.配置Swagger

為了方便測試首先在swagger config中配置加入令牌項,配置token作為請求頭部參數:

@Configuration @EnableSwagger2 public class SwaggerConfig {/** 是否開啟swagger */@Value("true")private boolean enabled;/*** 創建API*/@Beanpublic Docket createRestApi() {// 添加請求參數,我們這里把token作為請求頭部參數傳入后端ParameterBuilder parameterBuilder = new ParameterBuilder();List<Parameter> parameters = new ArrayList<Parameter>();parameterBuilder.name("token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build(); //關鍵是這里,將token作為了請求頭參數parameters.add(parameterBuilder.build());return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(enabled).select().apis(RequestHandlerSelectors.basePackage("com.english.english_vision")).paths(PathSelectors.any()).build().globalOperationParameters(parameters);}private ApiInfo apiInfo() {.....} }

這樣寫了以后運行會發現在請求時多了一行token:

在登陸成功后會返回token,用這個token去請求其他接口即可

2.spring security配置

新建securityconfig類

@Primary //由于報There is more than one bean of 'UserDetailsService' 所以我在類上面加了@primary 不過不加也不影響運行 @Configuration @EnableWebSecurity // 開啟Spring Security @EnableGlobalMethodSecurity(prePostEnabled = true) // 開啟權限注解,如:@PreAuthorize注解 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;//UserDetailsService 是rpingsecurity中自己的接口@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {// 使用自定義身份驗證組件auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 禁用 csrf, 由于使用的是JWT,我們這里不需要csrfhttp.cors().and().csrf().disable().authorizeRequests()// 跨域預檢請求.antMatchers(HttpMethod.OPTIONS, "/**").permitAll().antMatchers("/user/login").permitAll()//這里的路徑可以自己配置.antMatchers("/security/save").permitAll()//注冊.antMatchers("/security/login").permitAll()//登錄// swagger.antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/v2/api-docs").permitAll().antMatchers("/webjars/springfox-swagger-ui/**").permitAll()// 服務監控.antMatchers("/actuator/**").permitAll()// 其他所有請求需要身份認證.anyRequest().authenticated();// 退出登錄處理器http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());// token驗證過濾器http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);}@Bean@Overridepublic AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}}

3.用戶校驗邏輯

spring security整合jwt的代碼較多,目錄如下:

注冊和登錄接口

@RestController @RequestMapping("/security") @Api(tags = {"安全登錄接口"}) public class UserSecurityController {@Autowiredprivate IUserSecurityService securityService;@Autowiredprivate AuthenticationManager authenticationManager;@ApiOperation(value = "注冊或者修改用戶")@PostMapping(value = "/save")public ResponseResult save(UserSecurity record) {boolean save=false;UserSecurity userSecurity = securityService.getById(record.getId());if (record.getPassword() != null) {String salt = PasswordUtils.getSalt();if (userSecurity == null) {// 新增用戶if (securityService.selectByName(record.getName()) != null) {return ResponseResult.error("用戶名已存在!");}String password = PasswordUtils.encode(record.getPassword(), salt); record.setSalt(salt);record.setPassword(password);} else {// 修改用戶, 且修改了密碼if (!record.getPassword().equals(userSecurity.getPassword())) {String password = PasswordUtils.encode(record.getPassword(), salt);record.setSalt(salt);record.setPassword(password);}}save = securityService.save(record);}if (save) {return ResponseResult.success(record);} else {return ResponseResult.error(ResponseEnum.ERROR);}}@ApiOperation(value = "用戶登錄")@PostMapping(value = "/login")public ResponseResult login(@RequestBody LoginBean loginBean, HttpServletRequest request){String username = loginBean.getName();String password = loginBean.getPassword(); // 用戶信息UserSecurity user = securityService.selectByName(username);// 賬號不存在、密碼錯誤if (user == null) {return ResponseResult.error("賬號不存在");} if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) {return ResponseResult.error("密碼不正確");}// 系統登錄認證JwtAuthenticatioToken token = SecurityUtils.login(request, username, password, authenticationManager);return ResponseResult.success(token);} }

加密是:
String password = PasswordUtils.encode(record.getPassword(), salt);
驗證是:
if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) {
}
加密時只會對明文密碼進行加密然后與數據庫進行匹配,不會對密文解密,這樣更加安全。

dao層 service層 pojo層

public interface IUserSecurityService extends IService<UserSecurity> {UserSecurity selectByName(String username);Set<String> findPermissions(String userName); }@Service public class UserSecurityServiceImpl extends ServiceImpl<UserSecurityMapper, UserSecurity> implements IUserSecurityService { @Autowired private UserSecurityMapper securityMapper;@Overridepublic UserSecurity selectByName(String username) {return securityMapper.selectByName(username);}@Overridepublic Set<String> findPermissions(String userName) {return securityMapper.findPermissions(userName);} }

loginbean:

/*** @Author* @Description 登錄接口封裝對象* @Date**/ @Data public class LoginBean {private String name;private String password;// private String captcha;}

用戶pojo:

@NoArgsConstructor @AllArgsConstructor @Data @EqualsAndHashCode(callSuper = false) @TableName("t_user_security") @ApiModel("用戶安全管理") public class UserSecurity implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(name = "id", value = "用戶id", dataType = "Integer")@TableId(value = "id", type = IdType.AUTO)private Integer id=null;@ApiModelProperty(name = "name", value = "用戶名", dataType = "String")private String name;@ApiModelProperty(name = "password", value = "用戶密碼", dataType = "String")private String password;@ApiModelProperty(name = "salt", value = "密碼鹽", dataType = "String")private String salt;@ApiModelProperty(name = "perm", value = "用戶權限", dataType = "String")private String perm; }

mapper:

@Mapper public interface UserSecurityMapper extends BaseMapper<UserSecurity> {@Select("select * from t_user_security where name=#{username}")UserSecurity selectByName(String username);@Select("select perm from t_user_security where name=#{username}")Set<String> findPermissions(String userName); }

4.加密驗證邏輯

/*** 密碼工具類*/ public class PasswordUtils {/*** 匹配密碼* @param salt 鹽* @param rawPass 明文 * @param encPass 密文* @return*/public static boolean matches(String salt, String rawPass, String encPass) {return new PasswordEncoder(salt).matches(encPass, rawPass);}/*** 明文密碼加密* @param rawPass 明文* @param salt* @return*/public static String encode(String rawPass, String salt) {return new PasswordEncoder(salt).encode(rawPass);}/*** 獲取加密鹽* @return*/public static String getSalt() {return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);} }

加密:

public class PasswordEncoder {private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d","e", "f" };private final static String MD5 = "MD5";private final static String SHA = "SHA";private Object salt;private String algorithm;public PasswordEncoder(Object salt) {this(salt, MD5);}public PasswordEncoder(Object salt, String algorithm) {this.salt = salt;this.algorithm = algorithm;}/*** 密碼加密* @param rawPass* @return*/public String encode(String rawPass) {String result = null;try {System.out.println("加密"+rawPass);MessageDigest md = MessageDigest.getInstance(algorithm);// 加密后的字符串result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));} catch (Exception ex) {}return result;}/*** 密碼匹配驗證* @param encPass 密文* @param rawPass 明文* @return*/public boolean matches(String encPass, String rawPass) {String pass1 = "" + encPass;String pass2 = encode(rawPass);System.out.println("密文"+encPass);System.out.println(rawPass+"加密后"+pass2);return pass1.equals(pass2);}private String mergePasswordAndSalt(String password) {if (password == null) {password = "";}if ((salt == null) || "".equals(salt)) {return password;} else {return password + "{" + salt.toString() + "}";}}/*** 轉換字節數組為16進制字串* * @param b* 字節數組* @return 16進制字串*/private String byteArrayToHexString(byte[] b) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++) {resultSb.append(byteToHexString(b[i]));}return resultSb.toString();}/*** 將字節轉換為16進制* @param b* @return*/private static String byteToHexString(byte b) {int n = b;if (n < 0)n = 256 + n;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}

5.生成令牌邏輯

身份驗證提供者:

package com.english.english_vision.config.springSecurity;import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService;/*** @Author* @Description 身份驗證提供者* @Date 10.19**/ public class JwtAuthenticationProvider extends DaoAuthenticationProvider {public JwtAuthenticationProvider(UserDetailsService userDetailsService) {setUserDetailsService(userDetailsService);}@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();String salt = ((JwtUserDetails) userDetails).getSalt();// 覆寫密碼驗證邏輯if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}}

自定義令牌對象:

package com.english.english_vision.config.springSecurity;import java.util.Collection;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority;public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken {private static final long serialVersionUID = 1L;private String token;public JwtAuthenticatioToken(Object principal, Object credentials){super(principal, credentials);}public JwtAuthenticatioToken(Object principal, Object credentials, String token){super(principal, credentials);this.token = token;}public JwtAuthenticatioToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String token) {super(principal, credentials, authorities);this.token = token;}public String getToken() {return token;}public void setToken(String token) {this.token = token;}public static long getSerialversionuid() {return serialVersionUID;}}

JwtAuthenticatioToken繼承自UsernamePasswordAuthenticationToken,
UsernamePasswordAuthenticationToken繼承AbstractAuthenticationToken實現Authentication
所以當在頁面中輸入用戶名和密碼之后首先會進入到UsernamePasswordAuthenticationToken驗(Authentication),
然后生成的Authentication會被交由AuthenticationManager來進行管理,而AuthenticationManager管理一系列的AuthenticationProvider,每一個Provider都會通UserDetailsService和UserDetail來返回一個以UsernamePasswordAuthenticationToken實現的帶用戶名和密碼以及權限的Authentication

6.登錄認證過濾器:

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {@Autowiredpublic JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);} //AuthenticationManager這個接口方法入參和返回值的類型都是Authentication。該接口的作用是對用戶的未授信憑據進行認證,認證通過則返回授信狀態的憑據,否則將拋出認證異常AuthenticationException。@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {// 獲取token, 并檢查登錄狀態SecurityUtils.checkAuthentication(request);chain.doFilter(request, response);}}

BasicAuthenticationFilter:當一個HTTP請求中包含一個名字為Authorization的頭部,并且其值格式是Basic xxx時,該Filter會認為這是一個BASIC authorization頭部,其中xxx部分應該是一個base64編碼的{username}:{password}字符串。比如用戶名/密碼分別為 admin/secret, 則對應的該頭部是 : Basic YWRtaW46c2VjcmV0 。

該過濾器會從 HTTP BASIC authorization頭部解析出相應的用戶名和密碼然后調用AuthenticationManager進行認證,成功的話會把認證了的結果寫入到SecurityContextHolder中SecurityContext的屬性authentication上面。同時還會做其他一些處理,比如Remember Me相關處理等等。

7.JwtToken工具類:

public class JwtTokenUtils implements Serializable {private static final long serialVersionUID = 1L;/*** 用戶名稱*/private static final String USERNAME = Claims.SUBJECT;/*** 創建時間*/private static final String CREATED = "created";/*** 權限列表*/private static final String AUTHORITIES = "authorities";/*** 密鑰*/private static final String SECRET = "abcdefgh";/*** 有效期12小時*/private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000;/*** 生成令牌** @param userDetails 用戶* @return 令牌*/public static String generateToken(Authentication authentication) {Map<String, Object> claims = new HashMap<>(3);claims.put(USERNAME, SecurityUtils.getUsername(authentication));claims.put(CREATED, new Date());claims.put(AUTHORITIES, authentication.getAuthorities());return generateToken(claims);}/*** 從數據聲明生成令牌** @param claims 數據聲明* @return 令牌*/private static String generateToken(Map<String, Object> claims) {Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();}/*** 從令牌中獲取用戶名** @param token 令牌* @return 用戶名*/public static String getUsernameFromToken(String token) {String username;try {Claims claims = getClaimsFromToken(token);username = claims.getSubject();} catch (Exception e) {username = null;}return username;}/*** 根據請求令牌獲取登錄認證信息* @param token 令牌* @return 用戶名*/public static Authentication getAuthenticationeFromToken(HttpServletRequest request) {Authentication authentication = null;// 獲取請求攜帶的令牌String token = JwtTokenUtils.getToken(request);if(token != null) {// 請求令牌不能為空if(SecurityUtils.getAuthentication() == null) {// 上下文中Authentication為空Claims claims = getClaimsFromToken(token);if(claims == null) {return null;}String username = claims.getSubject();if(username == null) {return null;}if(isTokenExpired(token)) {return null;}Object authors = claims.get(AUTHORITIES);List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();if (authors != null && authors instanceof List) {for (Object object : (List) authors) {authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority")));}}authentication = new JwtAuthenticatioToken(username, null, authorities, token);} else {if(validateToken(token, SecurityUtils.getUsername())) {// 如果上下文中Authentication非空,且請求令牌合法,直接返回當前登錄認證信息authentication = SecurityUtils.getAuthentication();}}}return authentication;}/*** 從令牌中獲取數據聲明** @param token 令牌* @return 數據聲明*/private static Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;}/*** 驗證令牌* @param token* @param username* @return*/public static Boolean validateToken(String token, String username) {String userName = getUsernameFromToken(token);return (userName.equals(username) && !isTokenExpired(token));}/*** 刷新令牌* @param token* @return*/public static String refreshToken(String token) {String refreshedToken;try {Claims claims = getClaimsFromToken(token);claims.put(CREATED, new Date());refreshedToken = generateToken(claims);} catch (Exception e) {refreshedToken = null;}return refreshedToken;}/*** 判斷令牌是否過期** @param token 令牌* @return 是否過期*/public static Boolean isTokenExpired(String token) {try {Claims claims = getClaimsFromToken(token);Date expiration = claims.getExpiration();return expiration.before(new Date());} catch (Exception e) {return false;}}/*** 獲取請求token* @param request* @return*/public static String getToken(HttpServletRequest request) {String token = request.getHeader("Authorization");String tokenHead = "Bearer ";if(token == null) {token = request.getHeader("token");} else if(token.contains(tokenHead)){token = token.substring(tokenHead.length());} if("".equals(token)) {token = null;}return token;}}

security工具類

public class SecurityUtils {/*** 系統登錄認證* @param request* @param username* @param password* @param authenticationManager* @return*/public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password);token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 執行登錄認證過程Authentication authentication = authenticationManager.authenticate(token);// 認證成功存儲認證信息到上下文SecurityContextHolder.getContext().setAuthentication(authentication);// 生成令牌并返回給客戶端token.setToken(JwtTokenUtils.generateToken(authentication));return token;}/*** 獲取令牌進行認證* @param request*/public static void checkAuthentication(HttpServletRequest request) {// 獲取令牌并根據令牌獲取登錄認證信息Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request);// 設置登錄認證信息到上下文SecurityContextHolder.getContext().setAuthentication(authentication);}/*** 獲取當前用戶名* @return*/public static String getUsername() {String username = null;Authentication authentication = getAuthentication();if(authentication != null) {Object principal = authentication.getPrincipal();if(principal != null && principal instanceof UserDetails) {username = ((UserDetails) principal).getUsername();}}return username;}/*** 獲取用戶名* @return*/public static String getUsername(Authentication authentication) {String username = null;if(authentication != null) {Object principal = authentication.getPrincipal();if(principal != null && principal instanceof UserDetails) {username = ((UserDetails) principal).getUsername();}}return username;}/*** 獲取當前登錄信息* @return*/public static Authentication getAuthentication() {if(SecurityContextHolder.getContext() == null) {return null;}Authentication authentication = SecurityContextHolder.getContext().getAuthentication();return authentication;}}

8.權限封裝:

public class GrantedAuthorityImpl implements GrantedAuthority {private static final long serialVersionUID = 1L;private String authority;public GrantedAuthorityImpl(String authority) {this.authority = authority;}public void setAuthority(String authority) {this.authority = authority;}@Overridepublic String getAuthority() {return this.authority;} }

9.用戶模型:

public class JwtUserDetails implements UserDetails {private static final long serialVersionUID = 1L;private String username;private String password;private String salt;private Collection<? extends GrantedAuthority> authorities;JwtUserDetails(String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {this.username = username;this.password = password;this.salt = salt;this.authorities = authorities;}@Overridepublic String getUsername() {return username;}@JsonIgnore@Overridepublic String getPassword() {return password;}public String getSalt() {return salt;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@JsonIgnore@Overridepublic boolean isAccountNonExpired() {return true;}@JsonIgnore@Overridepublic boolean isAccountNonLocked() {return true;}@JsonIgnore@Overridepublic boolean isCredentialsNonExpired() {return true;}@JsonIgnore@Overridepublic boolean isEnabled() {return true;} }

10.測試生成令牌

配置好以后先注冊:

返回注冊成功的信息:

然后測試登錄:

結果:

可以看到返回的token,復制下來然后用這個token測試其他接口即可

11.測試用戶權限

首先在不加權限注解時測試這個接口:

@ApiOperation(value="管理員添加某個單詞")@PostMapping("admin/uploadword")public ResponseResult uploadWord(@RequestParam("wordfile")MultipartFile file,Word word) throws IOException {System.out.println("my CopyrightYear:"+new Myconfig().getCopyrightYear());String Wordpath = uploadUtils.upload(new Myconfig().getWordPath(),file); word.setImg(Wordpath); if(wordService.insert(word)>0) {return ResponseResult.success(word); } else {return ResponseResult.error(ResponseEnum.ERROR); }}

用剛剛生成的令牌進行測試:

可以看到添加成功:

現在在這個接口方法上加上權限注解:
@PreAuthorize(“hasAuthority(‘sys::manage’)”) ,重新運行:
由于剛剛登錄的用戶權限是sys::use,所以無法訪問這個接口:

現在重新登錄一個有sys::manage權限的用戶,獲得token,再次訪問這個接口:

訪問成功。

參考:https://blog.csdn.net/andy_zhang2007/article/details/84920910

https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1319904585363980289&__biz=MzUzMzQ2MDIyMA==#wechat_redirect

總結

以上是生活随笔為你收集整理的用spring security设置用户jwt令牌和设置接口访问权限案例的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。