做者從hallo world開始到目前的菜鳥水平,幾乎接觸過的每一個項目都避不開登陸這個功能。從最開始的不瞭解會話機制,密碼帳戶校驗成功只管往session裏存我的信息,到後來的瞭解無狀態、有狀態的登錄的各個方案,不得不說學習得是一個積累得過程,想要完整的瞭解一門技術,一個技術棧,得系統的去學習。想起了以前面試過一個兩年技術的小哥,不知道出於什麼想法,他在簡歷的末尾來了4個餅圖,以百分比的形式去展現掌握程度,當時沒忍住笑了出來(固然不是面試的時候)——java 75%,mysql80%,orcle75%,linux80%,面試一問你看過hashmap的源碼,瞭解底層原理嗎?額~...沒有,不知道。那你用過觸發器,知道什麼是存儲過程嗎?額~...沒有用過...html
其實這種狀況不少見,一些年輕的求職者找工做時用的簡歷都很粗糙,包括做者本身的。可是必定要注意對於本身會的技術棧,你掌握的程度必定要用對關鍵詞。好比一個一年技術經驗的java開發,簡歷上寫着各類掌握、熟練掌握springboot,稍微好一點的公司應該會直接把你刷掉,缺人的公司可能會約面試,可是面試官通常都會很反感。固然若是天賦異稟,真的能在這麼短的時間內達到掌握的程度,那我也無話可說,建議直接投aliP8。java
登陸身份存儲的方案其實不少,但出於不少的項目都須要去考慮項目的安全和權限體系,如今流行的權限框架有shiro,和springsecurity,前者相對用戶羣體會大一些。而登陸總體分爲兩大類:無狀態和有狀態。mysql
有狀態是指用戶與服務器進行會話,使用session等方式保持,服務端會存留用戶的會話信息。linux
而無狀態是如今比較流行的方式,通俗的說,服務器不去保留你用戶會話的信息,用戶來訪經過認證受權以後給你的token,存於用戶端,服務端會從token中解析其中的信息,而後根據這些信息去作業務操做。在解析token以前,並不能知道你是誰,服務端也不保留token,不使用session,在訪問量比較大的狀況下會減少服務器壓力。web
所以登陸方案的選擇主要仍是看具體的項目規模,業務的需求。面試
本文(本項目)主要介紹的是Spring security結合jwt、redis去實現登陸。redis
由於業務需求,做者在無狀態的基礎上,將token進行了惟一處理,意味着單點登陸,單token有效處理,採用redis去實現。所以,打破了無狀態。主要是由於單一的jwt的認證方式有一些缺點:spring
spring security 的核心功能主要包括:sql
spring security 能夠和shiro同樣實現權限的控制,且相比shiro功能會更強大。Spring Security能夠爲 Spring 應用提供聲明式的安全訪問控制,經過一系列在spring應用上下文中可配置的bean,利用spring ioc 和aop 等功能特性來提供聲明式的安全訪問控制功能(經過註解來控制接口和方法的訪問權限),減小重複的工做。數據庫
JSON Web Token (JWT),是在網絡應用間傳遞信息的一種基於 JSON的開放標準,用戶JSON對象在不一樣系統中進行信息的安全傳輸,主要使用場景用於用戶身份提供者和服務提供者間傳遞被認證的用戶身份信息。
對於Spring security和jwt本文就不作過多的介紹了,着重於實現。
1) pom導入:
<!-- 此處僅展現security與jwt --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
2) 開始配置文件:
SecurityConfig:
package com.ssk.shop.config.security; import com.ssk.shop.config.security.token.JwtTokenUtil; import com.ssk.shop.config.security.token.RestAuthenticationEntryPoint; import com.ssk.shop.config.security.token.RestfulAccessDeniedHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.security.web.firewall.HttpFirewall; import javax.sql.DataSource; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private BCryptPasswordEncoder passwordEncoder; /** * 註冊沒有權限的處理器 */ @Bean public RestfulAccessDeniedHandler restfulAccessDeniedHandler() { return new RestfulAccessDeniedHandler(); } @Bean public RestAuthenticationEntryPoint restAuthenticationEntryPoint() { return new RestAuthenticationEntryPoint(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 認證用戶的來源(內存或者數據庫) protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder); } //配置springSecurity相關信息artisanType/get_all protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests(); //不須要保護的資源路徑容許訪問 for (String url : ignoreUrlsConfig().getUrls()) { registry.antMatchers(url).permitAll(); } //容許跨域請求的OPTIONS請求 registry.antMatchers(HttpMethod.OPTIONS) .permitAll(); // 任何請求須要身份認證 // 釋放靜態資源,指定資源攔截規則,指定自定義認證頁面,指定退出認證配置,csrf配置 registry.and() .authorizeRequests() .anyRequest() .authenticated() // 關閉跨站請求防禦及不使用session .and() .csrf() .disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 自定義權限拒絕處理類 .and() .exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler()) .authenticationEntryPoint(restAuthenticationEntryPoint()) // 自定義權限攔截器JWT過濾器 .and() .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); } @Autowired DataSource dataSource; @Bean public PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); // jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Bean public IgnoreUrlsConfig ignoreUrlsConfig() { return new IgnoreUrlsConfig(); } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { return new JwtAuthenticationTokenFilter(); } @Bean public JwtTokenUtil jwtTokenUtil() { return new JwtTokenUtil(); } @Bean public HttpFirewall httpFirewall() { return new DefaultHttpFirewall(); } }
JwtTokenUtil:
package com.ssk.shop.config.security.token; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * jwt token生成工具 * * @author ssk * @since 2020-11-03 08:09:00 */ public class JwtTokenUtil { private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class); private static final String CLAIM_KEY_USERNAME = "sub"; private static final String CLAIM_KEY_CREATED = "created"; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; @Value("${jwt.tokenHead}") private String tokenHead; /** * 根據負責生成JWT的token */ private String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 從token中獲取JWT中的負載 */ private Claims getClaimsFromToken(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { LOGGER.info("JWT格式驗證失敗:{}", token); } return claims; } /** * 生成token的過時時間 */ private Date generateExpirationDate() { return new Date(System.currentTimeMillis() + expiration * 1000); } /** * 從token中獲取登陸用戶名 */ public String getUserNameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 驗證token是否還有效 * * @param token 客戶端傳入的token * @param userDetails 從數據庫中查詢出來的用戶信息 */ public boolean validateToken(String token, UserDetails userDetails) { String username = getUserNameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } /** * 判斷token是否已經失效 */ private boolean isTokenExpired(String token) { Date expiredDate = getExpiredDateFromToken(token); return expiredDate.before(new Date()); } /** * 從token中獲取過時時間 */ private Date getExpiredDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } /** * 根據用戶信息生成token */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } /** * 當原來的token沒過時時是能夠刷新的 * * @param oldToken 帶tokenHead的token */ public String refreshHeadToken(String oldToken) { if(StrUtil.isEmpty(oldToken)){ return null; } String token = oldToken.substring(tokenHead.length()); if(StrUtil.isEmpty(token)){ return null; } //token校驗不經過 Claims claims = getClaimsFromToken(token); if(claims==null){ return null; } //若是token已通過期,不支持刷新 if(isTokenExpired(token)){ return null; } //若是token在30分鐘以內剛刷新過,返回原token if(tokenRefreshJustBefore(token,30*60)){ return token; }else{ claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } } /** * 判斷token在指定時間內是否剛剛刷新過 * @param token 原token * @param time 指定時間(秒) */ private boolean tokenRefreshJustBefore(String token, int time) { Claims claims = getClaimsFromToken(token); Date created = claims.get(CLAIM_KEY_CREATED, Date.class); Date refreshDate = new Date(); //刷新時間在建立時間的指定時間內 if(refreshDate.after(created)&&refreshDate.before(DateUtil.offsetSecond(created,time))){ return true; } return false; } }
jwt 過濾器:
package com.ssk.shop.config.security; import com.ssk.shop.config.security.token.JwtTokenUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import javax.annotation.Resource; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * jwt 過濾器 * * @author ssk * @since 2020-11-03 08:09:00 */ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Resource private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader(this.tokenHeader); if (authHeader != null && authHeader.startsWith(this.tokenHead)) { String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer " String username = jwtTokenUtil.getUserNameFromToken(authToken); LOGGER.info("checking username:{}", username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); LOGGER.info("authenticated user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } }
此處若是使用的是單項目多類型用戶,且都想進行登陸控制,(admin可能涉及到不少的角色和權限控制,而用戶端並不會設計,而是採用單用戶角色)能夠將admin和user(其餘用戶)都繼承security的UserDetailsService,而後登陸以後的UserDetail進行從新封裝,最好是能放入角色,而後jwt驗證的時候根據id拿出Userdetail信息中的角色來進行相對應的層從新受權,實例代碼:
//將繼承了UserDetail的類都注入 @Resource private IAdminInfoFacade adminDetailsService; @Resource private IUserInfoFacade userDetailsFacade; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader(this.tokenHeader); if (authHeader != null && authHeader.startsWith(this.tokenHead)) { String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer " if (jwtTokenUtil.validateTokenSingle(authToken)){ String username = jwtTokenUtil.getUserNameFromToken(authToken); //根據token解析完以後拿到角色 ADMIN/USER String role = jwtTokenUtil.getUserRoleFromToken(authToken); LOGGER.info("checking username:{}", username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = null; //更具角色來選擇進入那個實現類進行從新受權校驗 if (role.equals(UserRoleTypeEnum.ADMIN.getType()) || role.equals(UserRoleTypeEnum.SUPER.getType())){ userDetails = this.adminDetailsService.loadUserByUsername(username); }else if (role.equals(UserRoleTypeEnum.USER.getType())){ userDetails = this.userDetailsFacade.loadUserByUsername(username); } if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); LOGGER.info("authenticated user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); System.out.println("//"); } } } }
可是這種方法處理建議不去使用security自帶的登陸,而是採用每種角色進行登陸接口的實現。
自定義未受權返回:
package com.ssk.shop.config.security.token; import cn.hutool.json.JSONUtil; import com.ssk.utils.CommonResult; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定義返回結果:沒有權限訪問時 * @author ssk * @since 2020-11-03 08:08:40 */ public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Cache-Control","no-cache"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage()))); response.getWriter().flush(); } }
自定義未登陸/未認證返回:
package com.ssk.shop.config.security.token; import cn.hutool.json.JSONUtil; import com.ssk.utils.CommonResult; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定義返回結果:未登陸或登陸過時 * @author ssk * @since 2020-11-03 08:08:40 */ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Cache-Control","no-cache"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage()))); response.getWriter().flush(); } }
jwt: tokenHeader: Authorization #JWT存儲的請求頭 secret: jwt_key #JWT加解密使用的密鑰 expiration: 604800 #JWT的超期限時間(60*60*24) tokenHead: Bearer #JWT負載中拿到開頭 #security不用受權的放行名單 須要結合上述的security配置文件 secure: ignored: urls: #安全路徑白名單 - /swagger-ui.html - /swagger-resources/** - /swagger/** - /**/v2/api-docs - /webjars/springfox-swagger-ui/** - /actuator/** - /druid/** - /admin-info/login - /admin/register - /admin/info - /admin/logout
以上即是全部的基礎配置,jwt的密鑰以及頭我是直接放入配置文件中。
此處使用swagger直接進行測試,使用的登陸方法是本身進行重寫
@Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @ApiOperation(value = "登陸之後返回token") @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public CommonResult<Map<String, String>> login(@RequestBody UserLoginVO user) { try { String token = adminInfoFacade.login(user.getUsername(), user.getPassword()); Map<String, String> tokenMap = new HashMap<>(); tokenMap.put("token", token); tokenMap.put("tokenHead", tokenHead); return CommonResult.success(tokenMap); }catch (ServiceException e){ return CommonResult.failed(e.getMessage()); } }
將security的loadUserByUsername進行重寫,而後自定義一個登錄類LoginMan用於保存帳號信息:
private PasswordEncoder passwordEncoder; @Resource private JwtTokenUtil jwtTokenUtil; @Override public String login(String username, String password) { String token = null; try { LoginMan userDetails = this.loadUserByUsername(username); if(!passwordEncoder.matches(password,userDetails.getPassword())){ throw new BadCredentialsException("密碼不正確"); } UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); token = jwtTokenUtil.generateToken(userDetails); } catch (AuthenticationException e) { LOGGER.warn("登陸異常:{}", e.getMessage()); throw new ServiceException("用戶名或密碼錯誤"); } return token; }
LoginMan:根據本身的業務需求進行改寫,主要實現對應的接口:
package com.ssk.shop.dto; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.List; /** * 登陸用戶實體 * * @author ssk * @since 2020-11-03 08:09:00 */ public class LoginMan implements GrantedAuthority, UserDetails { /** * 實體id */ private String id; /** * 密碼 */ private String password; private Object manMsg; private String roleType; /** * 名稱 */ private String name; private List<String> permissions; private boolean enabled = false; public Object getManMsg() { return manMsg; } public void setManMsg(Object manMsg) { this.manMsg = manMsg; } @Override @JsonIgnore public boolean isAccountNonExpired() { // 賬戶是否過時 return true; } @Override @JsonIgnore public boolean isAccountNonLocked() { // 賬戶是否被凍結 return true; } // 賬戶密碼是否過時,通常有的密碼要求性高的系統會使用到,比較每隔一段時間就要求用戶重置密碼 @Override @JsonIgnore public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } public void setPassword(String password) { this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getRoleType() { return roleType; } public void setRoleType(String roleType) { this.roleType = roleType; } public List<String> getPermissions() { return permissions; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override @JsonIgnore public List<GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_" + roleType)); for (String permission : permissions) { authorities.add(new SimpleGrantedAuthority(permission)); } return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return name; } @Override @JsonIgnore public String getAuthority() { return null; } public LoginMan(String name, String password, String roleType, List<String> permissions, String id , Object manMsg) { this.name = name; this.password = password; this.roleType = roleType; this.permissions = permissions; this.id = id; this.manMsg = manMsg; } public LoginMan() { } @Override public String toString() { return "LoginMan{" + "id='" + id + ''' + ", password='" + password + ''' + ", manMsg=" + manMsg + ", roleType='" + roleType + ''' + ", name='" + name + ''' + ", permissions=" + permissions + ", enabled=" + enabled + '}'; } }
登陸:
#成功 { "code": 200, "message": "操做成功", "data": { "tokenHead": "Bearer", "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmcmlkYXkiLCJjcmVhdGVkIjoxNjA0NzA5NzkyMjI2LCJleHAiOjE2MDUzMTQ1OTJ9.EHoNsN1W1Q3sek64XGV0ubIXhnTBpcvjAezbzDOLQAhQnCzvO9f4ovCJUvtrLyzSexqvJcoOR8ccBEPd6HXFOw" } }
#失敗 { "code": 500, "message": "用戶名或密碼錯誤", "data": null }
而後將token給swagger全局(token須要加上頭,且與值之間用空格);
對須要權限的接口進行訪問:
@PreAuthorize("hasAuthority('ADMIN_SEE')") @RequestMapping("/query_page") @ApiOperation(value = "查詢管理員集合", notes = "查詢管理員集合", httpMethod = "GET") @ApiImplicitParams({ @ApiImplicitParam(name = "roleId", value = "角色ID", paramType = "query", dataType = "String"), @ApiImplicitParam(name = "account", value = "帳號", paramType = "query", dataType = "String"), @ApiImplicitParam(name = "name", value = "名字", paramType = "query", dataType = "String"), @ApiImplicitParam(name = "sex", value = "性別(0:男,1:女)", paramType = "query", dataType = "int"), @ApiImplicitParam(name = "enable", value = "狀態(1是啓用,0是禁用))", paramType = "query", dataType = "int"), @ApiImplicitParam(name = "phone", value = "電話", paramType = "query", dataType = "String"), @ApiImplicitParam(name = "page", value = "展現的頁數", required = true, paramType = "query", dataType = "int",example = "1"), @ApiImplicitParam(name = "pageSize", value = "每頁展現幾條", required = true, paramType = "query", dataType = "int",example = "10") }) public CommonResult<CommonPage<AdminListDto>> selectAdmin(@RequestParam(required = false) String roleId, @RequestParam(required = false) String account, @RequestParam(required = false) String name, @RequestParam(required = false) Integer sex, @RequestParam(required = false) Integer enable, @RequestParam(required = false) String phone, Integer page, Integer pageSize) { try { return CommonResult.success(adminInfoFacade.selectAdmin(roleId, account, name, sex, enable, phone, page, pageSize)); } catch (ServiceException e) { e.printStackTrace(); return CommonResult.failed(e.getMessage()); } }
成功獲取返回值:
{ "code": 200, "message": "操做成功", "data": { "total": 0, "size": 10, "pages": 0, "current": 1, "records": [ { "adminInfoId": "053d9349b5ca4c56a873053c2afbc6df", "account": "bjty", "password": "$2a$10$jSw6APBmhUzkNuSVFKEGR.yu/srrbwHUimpvllm1QZB9ifMf1wXry", "adminRole": "fcc5a1585f5b4e32bc283a3e232435c0", "roleName": "管理員2號", "adminDepartmentName": null, "name": "白開水", "sex": null, "phone": "18999999999", "enableState": 1, "createTime": "2020-08-25T02:28:47.000+0000" },
清空token後進行訪問:
{ "code": 401, "data": "Full authentication is required to access this resource", "message": "暫未登陸或token已通過期" }
總結:
項目中初期設定是後端管理員能夠擁有單角色,而後給角色進行權限分類,接口訪問經過權限或角色進行控制。我的的理解是security將帳號的權限以及角色在登陸時放入了session(啓用的狀況下),而後經過會話訪問時獲取session中的會話信息,從而拿到權限進行比較,知足了即可放行訪問。
security經過aop的方式進行權限校驗,經過註解進行權限比較
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")//角色 @PreAuthorize("hasAuthority('ADMIN_SEE')")//權限
其中對於角色,咱們須要在登陸時將角色初始化如權限集合時添加上「ROLE_」,用以標識角色。
public LoginMan(String name, String password, String roleType, List<String> permissions, String id , Object manMsg) { this.name = name; this.password = password; this.roleType = roleType; this.permissions = permissions; this.id = id; this.manMsg = manMsg; } @Override @JsonIgnore public List<GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_" + roleType)); for (String permission : permissions) { authorities.add(new SimpleGrantedAuthority(permission)); } return authorities; }
本文中實例化LoginMan(UserDetail)是將角色類型單獨做爲參數傳入。
若是未禁用session,還能夠經過獲取下列方式獲取session中的值:
/** * 獲取當前帳戶id * @return * @throws ServiceException */ public String getThisAccountId()throws ServiceException { try { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (ObjectHelper.isNotEmpty(principal) && principal.equals("anonymousUser")){ return null; }else { LoginMan thisMan = (LoginMan) principal; return thisMan.getId(); } }catch (Exception e){ return null; } }
後期將會禁用session,採用token保存的方式進行實現。