SpringCloud之Security
Spring Security是Spring提供的一個安全框架,提供認證和授權功能,最主要的是它提供了簡單的使用方式,同時又有很高的靈活性,簡單,靈活,強大。
我個人部落格系統採用的許可權框架就是Spring Security,正好整合到SpringCloud裡面。
一般系統里關於角色方面通常有這麼幾張表,角色表、用戶-角色表、菜單表、角色-菜單表等。
不過我個人部落格系統主要以wordpress作為參考,沿用其12張表,如圖:
一、導入Maven依賴
<properties> <jjwt.version>0.9.0</jjwt.version> <spring-security-jwt.version>1.0.9.RELEASE</spring-security-jwt.version> </properties> <!-- springsecurity--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>${spring-security-jwt.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency>
二、編寫Spring Security配置類
package com.springcloud.blog.admin.config; import com.springcloud.blog.admin.security.UserAuthenticationProvider; import com.springcloud.blog.admin.security.UserPermissionEvaluator; import com.springcloud.blog.admin.security.handler.*; import com.springcloud.blog.admin.security.jwt.JWTAuthenticationTokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; /** * SpringSecurity配置類 * @Author youcong */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) //開啟許可權註解,默認是關閉的 public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 自定義登錄成功處理器 */ @Autowired private UserLoginSuccessHandler userLoginSuccessHandler; /** * 自定義登錄失敗處理器 */ @Autowired private UserLoginFailureHandler userLoginFailureHandler; /** * 自定義註銷成功處理器 */ @Autowired private UserLogoutSuccessHandler userLogoutSuccessHandler; /** * 自定義暫無許可權處理器 */ @Autowired private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler; /** * 自定義未登錄的處理器 */ @Autowired private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler; /** * 自定義登錄邏輯驗證器 */ @Autowired private UserAuthenticationProvider userAuthenticationProvider; /** * 加密方式 * @Author youcong */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } /** * 注入自定義PermissionEvaluator */ @Bean public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){ DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler(); handler.setPermissionEvaluator(new UserPermissionEvaluator()); return handler; } /** * 配置登錄驗證邏輯 */ @Override protected void configure(AuthenticationManagerBuilder auth){ //這裡可啟用我們自己的登陸驗證邏輯 auth.authenticationProvider(userAuthenticationProvider); } /** * 配置security的控制邏輯 * @Author youcong * @Param http 請求 */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 不進行許可權驗證的請求或資源(從配置文件中讀取) .antMatchers(JWTConfig.antMatchers.split(",")).permitAll() // .antMatchers("/*").permitAll() // 其他的需要登陸後才能訪問 .anyRequest().authenticated() .and() // 配置未登錄自定義處理類 .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler) .and() // 配置登錄地址 .formLogin() .loginProcessingUrl("/login/userLogin") // 配置登錄成功自定義處理類 .successHandler(userLoginSuccessHandler) // 配置登錄失敗自定義處理類 .failureHandler(userLoginFailureHandler) .and() // 配置登出地址 .logout() .logoutUrl("/login/userLogout") // 配置用戶登出自定義處理類 .logoutSuccessHandler(userLogoutSuccessHandler) .and() // 配置沒有許可權自定義處理類 .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler) .and() // 開啟跨域 .cors() .and() // 取消跨站請求偽造防護 .csrf().disable(); // 基於Token不需要session http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用快取 http.headers().cacheControl(); // 添加JWT過濾器 http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager())); } }
三、編寫JWTConfig和application.yml增加jwt相關配置
package com.springcloud.blog.admin.config; import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * JWT配置類 * @Author youcong */ @Getter @Component @ConfigurationProperties(prefix = "jwt") public class JWTConfig { /** * 密鑰KEY */ public static String secret; /** * TokenKey */ public static String tokenHeader; /** * Token前綴字元 */ public static String tokenPrefix; /** * 過期時間 */ public static Integer expiration; /** * 不需要認證的介面 */ public static String antMatchers; public void setSecret(String secret) { this.secret = secret; } public void setTokenHeader(String tokenHeader) { this.tokenHeader = tokenHeader; } public void setTokenPrefix(String tokenPrefix) { this.tokenPrefix = tokenPrefix; } public void setExpiration(Integer expiration) { this.expiration = expiration * 1000; } public void setAntMatchers(String antMatchers) { this.antMatchers = antMatchers; } }
application.yml增加如下內容:
# JWT配置 jwt: # 密匙KEY secret: JWTSecret # HeaderKEY tokenHeader: Authorization # Token前綴字元 tokenPrefix: challenger- # 過期時間 單位秒 1天後過期=86400 7天後過期=604800 expiration: 86400 # 配置不需要認證的介面 antMatchers: /index/**,/login/**,/favicon.ico # 有效時間 validTime: 7
四、編寫過濾器處理類
1.UserLoginSuccessHandler.java
package com.springcloud.blog.admin.security.handler; import com.springcloud.blog.admin.config.JWTConfig; import com.springcloud.blog.admin.security.entity.SelfUserEntity; import com.springcloud.blog.admin.utils.AccessAddressUtil; import com.springcloud.blog.admin.utils.JWTTokenUtil; import com.springcloud.blog.admin.utils.RedisUtil; import com.springcloud.blog.admin.utils.ResultUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; /** * @Description 登錄成功處理類 * @Author youcong */ @Component public class UserLoginSuccessHandler implements AuthenticationSuccessHandler { /** * 登錄成功返回結果 * @Author youcong */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){ // 組裝JWT SelfUserEntity selfUserEntity = (SelfUserEntity) authentication.getPrincipal(); String token = JWTTokenUtil.createAccessToken(selfUserEntity); token = JWTConfig.tokenPrefix + token; // 封裝返回參數 Map<String,Object> resultData = new HashMap<>(); resultData.put("code","200"); resultData.put("msg", "登錄成功"); resultData.put("token",token); ResultUtil.responseJson(response,resultData); } }
2.UserLoginFailureHandler.java
package com.springcloud.blog.admin.security.handler; import com.springcloud.blog.admin.utils.ResultUtil; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Description 登錄失敗處理類 * @Author youcong */ @Component public class UserLoginFailureHandler implements AuthenticationFailureHandler { /** * 登錄失敗返回結果 * @Author youcong */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ // 這些對於操作的處理類可以根據不同異常進行不同處理 if (exception instanceof UsernameNotFoundException){ System.out.println("【登錄失敗】"+exception.getMessage()); ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶名不存在")); } if (exception instanceof LockedException){ System.out.println("【登錄失敗】"+exception.getMessage()); ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶被凍結")); } if (exception instanceof BadCredentialsException){ System.out.println("【登錄失敗】"+exception.getMessage()); ResultUtil.responseJson(response,ResultUtil.resultCode(500,"密碼錯誤")); } ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登錄失敗")); } }
3.UserLogoutSuccessHandler.java
package com.springcloud.blog.admin.security.handler; import com.springcloud.blog.admin.utils.DateUtil; import com.springcloud.blog.admin.utils.RedisUtil; import com.springcloud.blog.admin.utils.ResultUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; /** * 登出成功處理類 * @Author youcong */ @Component public class UserLogoutSuccessHandler implements LogoutSuccessHandler { /** * 用戶登出返回結果 * 這裡應該讓前端清除掉Token * @Author youcong */ @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){ Map<String,Object> resultData = new HashMap<>(); resultData.put("code","200"); resultData.put("msg", "登出成功"); SecurityContextHolder.clearContext(); ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData)); } }
4.UserAuthAccessDeniedHandler.java
package com.springcloud.blog.admin.security.handler; import com.springcloud.blog.admin.utils.ResultUtil; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Description 暫無許可權處理類 * @Author youcong */ @Component public class UserAuthAccessDeniedHandler implements AccessDeniedHandler { /** * 暫無許可權返回結果 * @Author youcong */ @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){ ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授權")); } }
5.UserAuthenticationEntryPointHandler.java
package com.springcloud.blog.admin.security.handler; import com.springcloud.blog.admin.utils.ResultUtil; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 用戶未登錄處理類 * @Author youcong */ @Component public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint { /** * 用戶未登錄返回結果 * @Author youcong */ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登錄")); } }
6.UserAuthenticationProvider.java
自定義登錄驗證這個類,需要根據實際情況重寫。通常來說改動不大。
package com.springcloud.blog.admin.security; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.springcloud.blog.admin.entity.Usermeta; import com.springcloud.blog.admin.entity.Users; import com.springcloud.blog.admin.security.entity.SelfUserEntity; import com.springcloud.blog.admin.service.UsermetaService; import com.springcloud.blog.admin.service.UsersService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; 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.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 自定義登錄驗證 * * @Author youcong */ @Component public class UserAuthenticationProvider implements AuthenticationProvider { @Autowired private UsersService usersService; @Autowired private UsermetaService usermetaService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 獲取表單輸入中返回的用戶名 String userName = (String) authentication.getPrincipal(); // 獲取表單中輸入的密碼 String password = (String) authentication.getCredentials(); // 查詢用戶是否存在 SelfUserEntity userInfo = usersService.getUserInfo(userName); if (userInfo.getUsername() == null || userInfo.getUsername() == "") { throw new UsernameNotFoundException("用戶名不存在"); } // 我們還要判斷密碼是否正確,這裡我們的密碼使用BCryptPasswordEncoder進行加密的 if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) { throw new BadCredentialsException("密碼不正確"); } // 還可以加一些其他資訊的判斷,比如用戶帳號已停用等判斷 if (userInfo.getStatus().equals("1")) { throw new LockedException("該用戶已被凍結"); } // 角色集合 Set<GrantedAuthority> authorities = new HashSet<>(); EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>(); roleWrapper.eq("user_id",userInfo.getUserId()); roleWrapper.eq("meta_key","wp_user_level"); // 查詢用戶角色 List<Usermeta> sysRoleEntityList = usermetaService.selectList(roleWrapper); for (Usermeta sysRoleEntity: sysRoleEntityList){ authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getMetaValue())); } userInfo.setAuthorities(authorities); // 進行登錄 return new UsernamePasswordAuthenticationToken(userInfo, password, authorities); } @Override public boolean supports(Class<?> authentication) { return true; } }
7.UserPermissionEvaluator.java
package com.springcloud.blog.admin.security; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.springcloud.blog.admin.entity.Usermeta; import com.springcloud.blog.admin.service.UsermetaService; import org.apache.catalina.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 自定義許可權註解驗證 * @Author youcong */ @Component public class UserPermissionEvaluator implements PermissionEvaluator { @Autowired private UsermetaService usermetaService; /** * hasPermission鑒權方法 * 這裡僅僅判斷PreAuthorize註解中的許可權表達式 * 實際中可以根據業務需求設計資料庫通過targetUrl和permission做更複雜鑒權 * 當然targetUrl不一定是URL可以是數據Id還可以是管理員標識等,這裡根據需求自行設計 * @Author youcong * @Param authentication 用戶身份(在使用hasPermission表達式時Authentication參數默認會自動帶上) * @Param targetUrl 請求路徑 * @Param permission 請求路徑許可權 * @Return boolean 是否通過 */ @Override public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) { // 獲取用戶資訊 Usermeta selfUserEntity =(Usermeta) authentication.getPrincipal(); // 查詢用戶許可權(這裡可以將許可權放入快取中提升效率) Set<String> permissions = new HashSet<>(); EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>(); roleWrapper.eq("user_id",selfUserEntity.getUserId()); roleWrapper.eq("meta_key","wp_user_level"); List<Usermeta> sysMenuEntityList = usermetaService.selectList(roleWrapper); for (Usermeta sysMenuEntity:sysMenuEntityList) { permissions.add(sysMenuEntity.getMetaValue()); } // 許可權對比 if (permissions.contains(permission.toString())){ return true; } return true; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; } }
五、編寫實體類
package com.springcloud.blog.admin.security.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; import java.util.Map; /** * SpringSecurity用戶的實體 * 注意:這裡必須要實現UserDetails介面 * * @Author youcong */ public class SelfUserEntity implements Serializable, UserDetails { private static final long serialVersionUID = 1L; /** * 用戶ID */ private Long userId; /** * 用戶名 */ private String username; /** * 密碼 */ private String password; /** * 狀態 */ private String status; /** * 顯示名稱 */ private String displayName; /** * 用戶參數 */ private Map<String, String> userParamMap; /** * 用戶角色 */ private Collection<GrantedAuthority> authorities; /** * 賬戶是否過期 */ private boolean isAccountNonExpired = false; /** * 賬戶是否被鎖定 */ private boolean isAccountNonLocked = false; /** * 證書是否過期 */ private boolean isCredentialsNonExpired = false; /** * 賬戶是否有效 */ private boolean isEnabled = true; public static long getSerialVersionUID() { return serialVersionUID; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public void setAuthorities(Collection<GrantedAuthority> authorities) { this.authorities = authorities; } public void setEnabled(boolean enabled) { isEnabled = enabled; } public void setStatus(String status) { this.status = status; } public String getStatus() { return status; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public Map<String, String> getUserParamMap() { return userParamMap; } public void setUserParamMap(Map<String, String> userParamMap) { this.userParamMap = userParamMap; } @Override public Collection<GrantedAuthority> getAuthorities() { return authorities; } @Override public boolean isAccountNonExpired() { return isAccountNonExpired; } @Override public boolean isAccountNonLocked() { return isAccountNonLocked; } @Override public boolean isCredentialsNonExpired() { return isCredentialsNonExpired; } @Override public boolean isEnabled() { return isEnabled; } }
六、編寫JWT介面請求攔截器
package com.springcloud.blog.admin.security.jwt; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.springcloud.blog.admin.config.JWTConfig; import com.springcloud.blog.admin.security.entity.SelfUserEntity; import com.springcloud.blog.admin.utils.CollectionUtil; import com.springcloud.blog.admin.utils.JWTTokenUtil; import com.springcloud.blog.admin.utils.RedisUtil; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.util.StringUtils; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * JWT介面請求校驗攔截器 * 請求介面時會進入這裡驗證Token是否合法和過期 * * @Author youcong */ public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter { public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 獲取請求頭中JWT的Token String tokenHeader = request.getHeader(JWTConfig.tokenHeader); if (null != tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) { try { // 截取JWT前綴 String token = tokenHeader.replace(JWTConfig.tokenPrefix, ""); // 解析JWT Claims claims = Jwts.parser() .setSigningKey(JWTConfig.secret) .parseClaimsJws(token) .getBody(); // 獲取用戶名 String username = claims.getSubject(); String userId = claims.getId(); if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(userId)) { // 獲取角色 List<GrantedAuthority> authorities = new ArrayList<>(); String authority = claims.get("authorities").toString(); if (!StringUtils.isEmpty(authority)) { List<Map<String, String>> authorityMap = JSONObject.parseObject(authority, List.class); for (Map<String, String> role : authorityMap) { if (!StringUtils.isEmpty(role)) { authorities.add(new SimpleGrantedAuthority(role.get("authority"))); } } } //組裝參數 SelfUserEntity selfUserEntity = new SelfUserEntity(); selfUserEntity.setUsername(claims.getSubject()); selfUserEntity.setUserId(Long.parseLong(claims.getId())); selfUserEntity.setAuthorities(authorities); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (ExpiredJwtException e) { System.out.println("Token過期"); } catch (Exception e) { System.out.println("Token無效"); } } filterChain.doFilter(request, response); return; } }
七、SpringSecurity用戶的業務實現
package com.springcloud.blog.admin.security.service; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.springcloud.blog.admin.entity.Users; import com.springcloud.blog.admin.security.entity.SelfUserEntity; import com.springcloud.blog.admin.service.UsersService; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; /** * SpringSecurity用戶的業務實現 * * @Author youcong */ @Component public class SelfUserDetailsService implements UserDetailsService { @Autowired private UsersService usersService; /** * 查詢用戶資訊 * * @Author youcong * @Param username 用戶名 * @Return UserDetails SpringSecurity用戶資訊 */ @Override public SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException { EntityWrapper<Users> wrapper = new EntityWrapper<>(); //郵箱正則表達式 String expr = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$"; //是否為郵箱 if (username.matches(expr)) { wrapper.eq("user_email", username); } else { wrapper.eq("user_login", username); } // 查詢用戶資訊 Users sysUserEntity = usersService.selectOne(wrapper); if (sysUserEntity != null) { // 組裝參數 SelfUserEntity selfUserEntity = new SelfUserEntity(); BeanUtils.copyProperties(sysUserEntity, selfUserEntity); return selfUserEntity; } return null; } }
八、Spring Security常用註解
1.@Secured
當@EnableGlobalMethodSecurity(securedEnabled=true)的時候,@Secured可以使用。
@PostMapping("/helloUser") @Secured({"ROLE_normal","ROLE_admin"}) public Map<String, Object> initDashboard() { Map<String, Object> result = new HashMap<>(); result.put(ResponseDict.RESPONSE_TITLE_KEY, "儀錶盤初始化"); result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard()); return ResultUtil.resultSuccess(result); }
說明:擁有normal或者admin角色的用戶都可以方法helloUser()方法。另外需要注意的是這裡匹配的字元串需要添加前綴「ROLE_「。
Spring的 @PreAuthorize/@PostAuthorize 註解更適合方法級的安全,也支援Spring 表達式語言,提供了基於表達式的訪問控制。
當@EnableGlobalMethodSecurity(prePostEnabled=true)的時候,@PreAuthorize可以使用:
@PostMapping("/initDashboard") @PreAuthorize("hasRole('100')") public Map<String, Object> initDashboard() { Map<String, Object> result = new HashMap<>(); result.put(ResponseDict.RESPONSE_TITLE_KEY, "儀錶盤初始化"); result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard()); return ResultUtil.resultSuccess(result); }
@PostAuthorize 註解使用並不多,在方法執行後再進行許可權驗證,適合驗證帶有返回值的許可權,Spring EL 提供 返回對象能夠在表達式語言中獲取返回的對象returnObject。
當@EnableGlobalMethodSecurity(prePostEnabled=true)的時候,@PostAuthorize可以使用:
@GetMapping("/getUserInfo") @PostAuthorize(" returnObject!=null && returnObject.username == authentication.name") public User getUserInfo() { Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); User user; if("anonymousUser".equals(pricipal)) { user = null; }else { user = (User) pricipal; } return user; }
九、測試
(1)登錄測試,拿到token,如圖:
(2)請求中如果不攜帶token的話,請求其它介面就會顯示沒有登錄的提示,如圖:
(3)正確的請求應當攜帶token,就像下面這樣,如圖:
(4)沒有許可權請求,如圖: