Spring Boot 整合 Spring Security 實(shí)戰(zhàn)!
作者:Sans_
鏈接:juejin.cn/post/6844903974546456590
一、說(shuō)明
SpringSecurity是一個(gè)用于Java 企業(yè)級(jí)應(yīng)用程序的安全框架,主要包含用戶認(rèn)證和用戶授權(quán)兩個(gè)方面.相比較Shiro而言,Security功能更加的強(qiáng)大,它可以很容易地?cái)U(kuò)展以滿足更多安全控制方面的需求,但也相對(duì)它的學(xué)習(xí)成本會(huì)更高,兩種框架各有利弊.實(shí)際開(kāi)發(fā)中還是要根據(jù)業(yè)務(wù)和項(xiàng)目的需求來(lái)決定使用哪一種.
JWT是在Web應(yīng)用中安全傳遞信息的規(guī)范,從本質(zhì)上來(lái)說(shuō)是Token的演變,是一種生成加密用戶身份信息的Token,特別適用于分布式單點(diǎn)登陸的場(chǎng)景,無(wú)需在服務(wù)端保存用戶的認(rèn)證信息,而是直接對(duì)Token進(jìn)行校驗(yàn)獲取用戶信息,使單點(diǎn)登錄更為簡(jiǎn)單靈活.
更多 Spring Boot 實(shí)戰(zhàn)可以關(guān)注公眾號(hào)「Java后端」,回復(fù) 666 即可。
二、項(xiàng)目環(huán)境
SpringBoot版本:2.1.6
SpringSecurity版本: 5.1.5
MyBatis-Plus版本: 3.1.0
JDK版本:1.8
數(shù)據(jù)表(SQL文件在項(xiàng)目中): 數(shù)據(jù)庫(kù)中測(cè)試號(hào)的密碼進(jìn)行了加密,密碼皆為123456:

Maven依賴如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Security依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- MybatisPlus 核心庫(kù) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 引入阿里數(shù)據(jù)庫(kù)連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- StringUtilS工具 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!-- JSON工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<!-- JWT依賴 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>配置如下:
# 配置端口
server:
port: 8764
spring:
# 配置數(shù)據(jù)源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sans_security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
# JWT配置
jwt:
# 密匙KEY
secret: JWTSecret
# HeaderKEY
tokenHeader: Authorization
# Token前綴字符
tokenPrefix: Sans-
# 過(guò)期時(shí)間 單位秒 1天后過(guò)期=86400 7天后過(guò)期=604800
expiration: 86400
# 配置不需要認(rèn)證的接口
antMatchers: /index/**,/login/**,/favicon.ico
# Mybatis-plus相關(guān)配置
mybatis-plus:
# xml掃描,多個(gè)目錄用逗號(hào)或者分號(hào)分隔(告訴 Mapper 所對(duì)應(yīng)的 XML 文件位置)
mapper-locations: classpath:mapper/*.xml
# 以下配置均有默認(rèn)值,可以不設(shè)置
global-config:
db-config:
#主鍵類(lèi)型 AUTO:"數(shù)據(jù)庫(kù)ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數(shù)字類(lèi)型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
#字段策略 IGNORED:"忽略判斷" NOT_NULL:"非 NULL 判斷") NOT_EMPTY:"非空判斷"
field-strategy: NOT_EMPTY
#數(shù)據(jù)庫(kù)類(lèi)型
db-type: MYSQL
configuration:
# 是否開(kāi)啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫(kù)列名到Java屬性駝峰命名的類(lèi)似映射
map-underscore-to-camel-case: true
# 返回map時(shí)true:當(dāng)查詢數(shù)據(jù)為空時(shí)字段返回為null,false:不加這個(gè)查詢數(shù)據(jù)為空時(shí),字段將被隱藏
call-setters-on-nulls: true
# 這個(gè)配置會(huì)將執(zhí)行的sql打印出來(lái),在開(kāi)發(fā)或測(cè)試的時(shí)候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl三、編寫(xiě)項(xiàng)目基礎(chǔ)類(lèi)
Entity,Dao,Service,及等SpringSecurity用戶的Entity,Service類(lèi)等在這里省略,請(qǐng)參考源碼
編寫(xiě)JWT工具類(lèi)
/**
* JWT工具類(lèi)
* @Author Sans
* @CreateTime 2019/10/2 7:42
*/
@Slf4j
public class JWTTokenUtil {
/**
* 生成Token
* @Author Sans
* @CreateTime 2019/10/2 12:16
* @Param selfUserEntity 用戶安全實(shí)體
* @Return Token
*/
public static String createAccessToken(SelfUserEntity selfUserEntity){
// 登陸成功生成JWT
String token = Jwts.builder()
// 放入用戶名和用戶ID
.setId(selfUserEntity.getUserId()+"")
// 主題
.setSubject(selfUserEntity.getUsername())
// 簽發(fā)時(shí)間
.setIssuedAt(new Date())
// 簽發(fā)者
.setIssuer("sans")
// 自定義屬性 放入用戶擁有權(quán)限
.claim("authorities", JSON.toJSONString(selfUserEntity.getAuthorities()))
// 失效時(shí)間
.setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration))
// 簽名算法和密鑰
.signWith(SignatureAlgorithm.HS512, JWTConfig.secret)
.compact();
return token;
}
}編寫(xiě)暫無(wú)權(quán)限處理類(lèi)
/**
* @Description 暫無(wú)權(quán)限處理類(lèi)
* @Author Sans
* @CreateTime 2019/10/3 8:39
*/
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler{
/**
* 暫無(wú)權(quán)限返回結(jié)果
* @Author Sans
* @CreateTime 2019/10/3 8:41
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){
ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授權(quán)"));
}
}編寫(xiě)登錄失敗處理類(lèi)
/**
* @Description 登錄失敗處理類(lèi)
* @Author Sans
* @CreateTime 2019/10/3 9:06
*/
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
/**
* 登錄失敗返回結(jié)果
* @Author Sans
* @CreateTime 2019/10/3 9:12
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
// 這些對(duì)于操作的處理類(lèi)可以根據(jù)不同異常進(jìn)行不同處理
if (exception instanceof UsernameNotFoundException){
log.info("【登錄失敗】"+exception.getMessage());
ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶名不存在"));
}
if (exception instanceof LockedException){
log.info("【登錄失敗】"+exception.getMessage());
ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶被凍結(jié)"));
}
if (exception instanceof BadCredentialsException){
log.info("【登錄失敗】"+exception.getMessage());
ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用戶名密碼不正確"));
}
ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登錄失敗"));
}
}編寫(xiě)登錄成功處理類(lèi)
/**
* @Description 登錄成功處理類(lèi)
* @Author Sans
* @CreateTime 2019/10/3 9:13
*/
@Slf4j
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
/**
* 登錄成功返回結(jié)果
* @Author Sans
* @CreateTime 2019/10/3 9:27
*/
@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;
// 封裝返回參數(shù)
Map<String,Object> resultData = new HashMap<>();
resultData.put("code","200");
resultData.put("msg", "登錄成功");
resultData.put("token",token);
ResultUtil.responseJson(response,resultData);
}
}編寫(xiě)登出成功處理類(lèi)
/**
* 用戶登出類(lèi)
* @Author Sans
* @CreateTime 2019/10/3 9:42
*/
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
/**
* 用戶登出返回結(jié)果
* 這里應(yīng)該讓前端清除掉Token
* @Author Sans
* @CreateTime 2019/10/3 9:50
*/
@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));
}
}四、編寫(xiě)Security核心類(lèi)
編寫(xiě)自定義登錄驗(yàn)證類(lèi)
/**
* 自定義登錄驗(yàn)證
* @Author Sans
* @CreateTime 2019/10/1 19:11
*/
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
@Autowired
private SelfUserDetailsService selfUserDetailsService;
@Autowired
private SysUserService sysUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 獲取表單輸入中返回的用戶名
String userName = (String) authentication.getPrincipal();
// 獲取表單中輸入的密碼
String password = (String) authentication.getCredentials();
// 查詢用戶是否存在
SelfUserEntity userInfo = selfUserDetailsService.loadUserByUsername(userName);
if (userInfo == null) {
throw new UsernameNotFoundException("用戶名不存在");
}
// 我們還要判斷密碼是否正確,這里我們的密碼使用BCryptPasswordEncoder進(jìn)行加密的
if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {
throw new BadCredentialsException("密碼不正確");
}
// 還可以加一些其他信息的判斷,比如用戶賬號(hào)已停用等判斷
if (userInfo.getStatus().equals("PROHIBIT")){
throw new LockedException("該用戶已被凍結(jié)");
}
// 角色集合
Set<GrantedAuthority> authorities = new HashSet<>();
// 查詢用戶角色
List<SysRoleEntity> sysRoleEntityList = sysUserService.selectSysRoleByUserId(userInfo.getUserId());
for (SysRoleEntity sysRoleEntity: sysRoleEntityList){
authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getRoleName()));
}
userInfo.setAuthorities(authorities);
// 進(jìn)行登錄
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}編寫(xiě)自定義PermissionEvaluator注解驗(yàn)證
/**
* 自定義權(quán)限注解驗(yàn)證
* @Author Sans
* @CreateTime 2019/10/6 13:31
*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
@Autowired
private SysUserService sysUserService;
/**
* hasPermission鑒權(quán)方法
* 這里僅僅判斷PreAuthorize注解中的權(quán)限表達(dá)式
* 實(shí)際中可以根據(jù)業(yè)務(wù)需求設(shè)計(jì)數(shù)據(jù)庫(kù)通過(guò)targetUrl和permission做更復(fù)雜鑒權(quán)
* @Author Sans
* @CreateTime 2019/10/6 18:25
* @Param authentication 用戶身份
* @Param targetUrl 請(qǐng)求路徑
* @Param permission 請(qǐng)求路徑權(quán)限
* @Return boolean 是否通過(guò)
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
// 獲取用戶信息
SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();
// 查詢用戶權(quán)限(這里可以將權(quán)限放入緩存中提升效率)
Set<String> permissions = new HashSet<>();
List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());
for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {
permissions.add(sysMenuEntity.getPermission());
}
// 權(quán)限對(duì)比
if (permissions.contains(permission.toString())){
return true;
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}編寫(xiě)SpringSecurity核心配置類(lèi)
/**
* SpringSecurity核心配置類(lèi)
* @Author Sans
* @CreateTime 2019/10/1 9:40
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //開(kāi)啟權(quán)限注解,默認(rèn)是關(guān)閉的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自定義登錄成功處理器
*/
@Autowired
private UserLoginSuccessHandler userLoginSuccessHandler;
/**
* 自定義登錄失敗處理器
*/
@Autowired
private UserLoginFailureHandler userLoginFailureHandler;
/**
* 自定義注銷(xiāo)成功處理器
*/
@Autowired
private UserLogoutSuccessHandler userLogoutSuccessHandler;
/**
* 自定義暫無(wú)權(quán)限處理器
*/
@Autowired
private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
/**
* 自定義未登錄的處理器
*/
@Autowired
private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
/**
* 自定義登錄邏輯驗(yàn)證器
*/
@Autowired
private UserAuthenticationProvider userAuthenticationProvider;
/**
* 加密方式
* @Author Sans
* @CreateTime 2019/10/1 14:00
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 注入自定義PermissionEvaluator
*/
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new UserPermissionEvaluator());
return handler;
}
/**
* 配置登錄驗(yàn)證邏輯
*/
@Override
protected void configure(AuthenticationManagerBuilder auth){
//這里可啟用我們自己的登陸驗(yàn)證邏輯
auth.authenticationProvider(userAuthenticationProvider);
}
/**
* 配置security的控制邏輯
* @Author Sans
* @CreateTime 2019/10/1 16:56
* @Param http 請(qǐng)求
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//不進(jìn)行權(quán)限驗(yàn)證的請(qǐng)求或資源(從配置文件中讀取)
.antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
//其他的需要登陸后才能訪問(wèn)
.anyRequest().authenticated()
.and()
//配置未登錄自定義處理類(lèi)
.httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
.and()
//配置登錄地址
.formLogin()
.loginProcessingUrl("/login/userLogin")
//配置登錄成功自定義處理類(lèi)
.successHandler(userLoginSuccessHandler)
//配置登錄失敗自定義處理類(lèi)
.failureHandler(userLoginFailureHandler)
.and()
//配置登出地址
.logout()
.logoutUrl("/login/userLogout")
//配置用戶登出自定義處理類(lèi)
.logoutSuccessHandler(userLogoutSuccessHandler)
.and()
//配置沒(méi)有權(quán)限自定義處理類(lèi)
.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
.and()
// 開(kāi)啟跨域
.cors()
.and()
// 取消跨站請(qǐng)求偽造防護(hù)
.csrf().disable();
// 基于Token不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 禁用緩存
http.headers().cacheControl();
// 添加JWT過(guò)濾器
http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
}
}五、編寫(xiě)JWT攔截類(lèi)
編寫(xiě)JWT接口請(qǐng)求校驗(yàn)攔截器
/**
* JWT接口請(qǐng)求校驗(yàn)攔截器
* 請(qǐng)求接口時(shí)會(huì)進(jìn)入這里驗(yàn)證Token是否合法和過(guò)期
* @Author Sans
* @CreateTime 2019/10/5 16:41
*/
@Slf4j
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 獲取請(qǐng)求頭中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")));
}
}
}
//組裝參數(shù)
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){
log.info("Token過(guò)期");
} catch (Exception e) {
log.info("Token無(wú)效");
}
}
filterChain.doFilter(request, response);
return;
}
}六、權(quán)限注解和hasPermission權(quán)限擴(kuò)展
Security允許我們?cè)诙xURL方法訪問(wèn)所應(yīng)有的注解權(quán)限時(shí)使用SpringEL表達(dá)式,在定義所需的訪問(wèn)權(quán)限時(shí)如果對(duì)應(yīng)的表達(dá)式返回結(jié)果為true則表示擁有對(duì)應(yīng)的權(quán)限,反之則沒(méi)有權(quán)限,會(huì)進(jìn)入到我們配置的UserAuthAccessDeniedHandler(暫無(wú)權(quán)限處理類(lèi))中進(jìn)行處理.這里舉一些例子,代碼中注釋有對(duì)應(yīng)的描述。

表達(dá)式
/**
* 管理端信息
* @Author Sans
* @CreateTime 2019/10/2 14:22
* @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
*/
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping(value = "/info",method = RequestMethod.GET)
public Map<String,Object> userLogin(){
Map<String,Object> result = new HashMap<>();
SelfUserEntity userDetails = SecurityUtil.getUserInfo();
result.put("title","管理端信息");
result.put("data",userDetails);
return ResultUtil.resultSuccess(result);
}
/**
* 擁有ADMIN或者USER角色可以訪問(wèn)
* @Author Sans
* @CreateTime 2019/10/2 14:22
* @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
*/
@PreAuthorize("hasAnyRole('ADMIN','USER')")
@RequestMapping(value = "/list",method = RequestMethod.GET)
public Map<String,Object> list(){
Map<String,Object> result = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
result.put("title","擁有用戶或者管理員角色都可以查看");
result.put("data",sysUserEntityList);
return ResultUtil.resultSuccess(result);
}
/**
* 擁有ADMIN和USER角色可以訪問(wèn)
* @Author Sans
* @CreateTime 2019/10/2 14:22
* @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
*/
@PreAuthorize("hasRole('ADMIN') and hasRole('USER')")
@RequestMapping(value = "/menuList",method = RequestMethod.GET)
public Map<String,Object> menuList(){
Map<String,Object> result = new HashMap<>();
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
result.put("title","擁有用戶和管理員角色都可以查看");
result.put("data",sysMenuEntityList);
return ResultUtil.resultSuccess(result);
}通常情況下使用hasRole和hasAnyRole基本可以滿足大部分鑒權(quán)需求,但是有時(shí)候面對(duì)更復(fù)雜的場(chǎng)景上述常規(guī)表示式無(wú)法完成權(quán)限認(rèn)證,Security也為我們提供了解決方案.通過(guò)hasPermission()來(lái)擴(kuò)展表達(dá)式.使用hasPermission()首先要實(shí)現(xiàn)PermissionEvaluator接口
/**
* 自定義權(quán)限注解驗(yàn)證
* @Author Sans
* @CreateTime 2019/10/6 13:31
*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
@Autowired
private SysUserService sysUserService;
/**
* hasPermission鑒權(quán)方法
* 這里僅僅判斷PreAuthorize注解中的權(quán)限表達(dá)式
* 實(shí)際中可以根據(jù)業(yè)務(wù)需求設(shè)計(jì)數(shù)據(jù)庫(kù)通過(guò)targetUrl和permission做更復(fù)雜鑒權(quán)
* 當(dāng)然targetUrl不一定是URL可以是數(shù)據(jù)Id還可以是管理員標(biāo)識(shí)等,這里根據(jù)需求自行設(shè)計(jì)
* @Author Sans
* @CreateTime 2019/10/6 18:25
* @Param authentication 用戶身份(在使用hasPermission表達(dá)式時(shí)Authentication參數(shù)默認(rèn)會(huì)自動(dòng)帶上)
* @Param targetUrl 請(qǐng)求路徑
* @Param permission 請(qǐng)求路徑權(quán)限
* @Return boolean 是否通過(guò)
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
// 獲取用戶信息
SelfUserEntity selfUserEntity =(SelfUserEntity) authentication.getPrincipal();
// 查詢用戶權(quán)限(這里可以將權(quán)限放入緩存中提升效率)
Set<String> permissions = new HashSet<>();
List<SysMenuEntity> sysMenuEntityList = sysUserService.selectSysMenuByUserId(selfUserEntity.getUserId());
for (SysMenuEntity sysMenuEntity:sysMenuEntityList) {
permissions.add(sysMenuEntity.getPermission());
}
// 權(quán)限對(duì)比
if (permissions.contains(permission.toString())){
return true;
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}在請(qǐng)求方法上添加hasPermission示例
/**
* 擁有sys:user:info權(quán)限可以訪問(wèn)
* hasPermission 第一個(gè)參數(shù)是請(qǐng)求路徑 第二個(gè)參數(shù)是權(quán)限表達(dá)式
* @Author Sans
* @CreateTime 2019/10/2 14:22
* @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
*/
@PreAuthorize("hasPermission('/admin/userList','sys:user:info')")
@RequestMapping(value = "/userList",method = RequestMethod.GET)
public Map<String,Object> userList(){
Map<String,Object> result = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
result.put("title","擁有sys:user:info權(quán)限都可以查看");
result.put("data",sysUserEntityList);
return ResultUtil.resultSuccess(result);
}hasPermission可以也可以和其他表達(dá)式聯(lián)合使用
/**
* 擁有ADMIN角色和sys:role:info權(quán)限可以訪問(wèn)
* @Author Sans
* @CreateTime 2019/10/2 14:22
* @Return Map<String,Object> 返回?cái)?shù)據(jù)MAP
*/
@PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')")
@RequestMapping(value = "/adminRoleList",method = RequestMethod.GET)
public Map<String,Object> adminRoleList(){
Map<String,Object> result = new HashMap<>();
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
result.put("title","擁有ADMIN角色和sys:role:info權(quán)限可以訪問(wèn)");
result.put("data",sysRoleEntityList);
return ResultUtil.resultSuccess(result);
}七、測(cè)試
創(chuàng)建賬戶這里用戶加密使用了Security推薦的bCryptPasswordEncoder方法
/**
* 注冊(cè)用戶
*/
@Test
public void contextLoads() {
// 注冊(cè)用戶
SysUserEntity sysUserEntity = new SysUserEntity();
sysUserEntity.setUsername("sans");
sysUserEntity.setPassword(bCryptPasswordEncoder.encode("123456"));
// 設(shè)置用戶狀態(tài)
sysUserEntity.setStatus("NORMAL");
sysUserService.save(sysUserEntity);
// 分配角色 1:ADMIN 2:USER
SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity();
sysUserRoleEntity.setRoleId(2L);
sysUserRoleEntity.setUserId(sysUserEntity.getUserId());
sysUserRoleService.save(sysUserRoleEntity);
}登錄USER角色賬號(hào),登錄成功后我們會(huì)獲取到身份認(rèn)證的Token

訪問(wèn)USER角色的接口,把上一步獲取到的Token設(shè)置在Headers中,Key為Authorization,我們之前實(shí)現(xiàn)的JWTAuthenticationTokenFilter攔截器會(huì)根據(jù)請(qǐng)求頭中的Authorization獲取并解析Token

使用USER角色Token訪問(wèn)ADMIN角色的接口,會(huì)被拒絕,告知未授權(quán)(暫無(wú)權(quán)限會(huì)進(jìn)入我們定義的UserAuthAccessDeniedHandler這個(gè)類(lèi)進(jìn)行處理)

更換ADMIN角色進(jìn)行登錄并訪問(wèn)ADMIN接口

八.項(xiàng)目源碼
碼云:gitee.com/liselotte/spring-boot-security-demo
GitHub:github.com/xuyulong2017/my-java-demo
一步步安裝Jumpserver堡壘機(jī)圖文詳解(官方教程版),從此爽歪歪! 零散的MySQL基礎(chǔ)總是記不住?看這一篇如何拯救你 相同字符串,但是equals為false?我多年的java白學(xué)了嗎? 華為面試官:為什么 HashMap 的加載因子是0.75? 趣頭條面試題:ThreadLocal是什么?怎么用?為什么用它?有什么缺點(diǎn) @Autowire和@Resource注解使用的正確姿勢(shì),這些年我一直用錯(cuò)了?。?/a> 強(qiáng)大:MyBatis 流式查詢 華為面試官:為什么 HashMap 的加載因子是0.75?


