一、前言
登錄和授權模塊是整個項目的安全鎖,是開發中的必要一環。本文通過使用SpringSecurity安全框架和JWT實現后台的登錄和授權,為了方便接口的在線測試,對swagger-ui的配置文件進行改造,使其能夠拿到登錄令牌token。
二、介紹
1、SpringSecurity
SpringSecurity是一個高性能的認證與授權的框架,為java項目提供了認證和授權的功能。
2、JWT
JWT是JSON WEB TOKEN的縮寫,它是基於RFC 7519標准定義的一種可以安全傳輸的JSON對象,由於使用了數字簽名,所以是安全可信任的。
JWT的完全體是token,token的組成分為三部分:header,payload,signature;
header存放的是生成簽名的算法標准
{"alg":"HS512"}
payload是驗證主體,存放用戶名,token的創建時間和過期時間
{"sub":"admin","created":"1574405893304","end":"1575010693"}
signature是以header和payload為主體生成的簽名驗證,一旦header或payload被篡改,簽名將驗證失敗。
String signature = HMACSHA512(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret);
JWT實例
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE1NzQ0MDU4OTMzMDQsImV4cCI6MTU3NTAxMDY5M30.TOM6eciLWaeDPUV5Q1CfISzN0H1-4TYCriKD2FOkcznHpkoJPDID105xdG0D_vlGlx0FweGvoMphISga7VAUmw
解析JWT,進入:https://jwt.io/
三、實現認證與授權的流程說明
1、用戶調用登錄接口進行登錄,成功后獲取到生成的token;
2、之后用戶每次調用接口都會在http的header請求頭中添加key為Authorization,值為獲取到的token;
3、后台過濾器會解析請求頭中Authorization的值token,校驗數字簽名,獲取封裝的用戶信息,實現認證與授權。
四、Hutool工具包
Hutool工具包可以幫助開發者簡化代碼,優化代碼。
五、mysql新建數據庫表
1、ums_admin:用戶表
2、ums_role:用戶角色表
3、ums_permission:用戶權限表
4、ums_admin_role_relation:用戶角色關系表
5、ums_role_permission_relation:角色權限關系表
6、ums_admin_permission_relation:用戶權限關系表
7、mybatis逆向生成相應的model,example,mapper,mapper.xml
resource包下修改generatorConfig.xml文件,添加生成表名
<!--生成全部表tableName設為%--> <!--商品品牌表--> <table tableName="pms_brand"></table> <!--用戶表--> <table tableName="ums_admin"></table> <!--角色表--> <table tableName="ums_role"></table> <!--權限表--> <table tableName="ums_permission"></table> <!--用戶角色關系表--> <table tableName="ums_admin_role_relation"></table> <!--角色權限關系表--> <table tableName="ums_role_permission_relation"></table> <!--用戶權限關系表--> <table tableName="ums_admin_permission_relation"></table>
運行mbg包下Generator程序,自動生成代碼
六、項目整合SpringSecurity安全框架
1、添加相關pom.xml依賴配置
<!--SpringSecurity依賴配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--hutool工具包依賴配置--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.5.7</version> </dependency> <!--JWT依賴配置--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
2、在application.yml中添加自定義jwt配置
#項目啟動端口 server: port: 10077 spring: #連接mysql數據庫 datasource: url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root #連接redis緩存數據庫 redis: host: localhost #redis服務器地址 database: 0 #redis數據庫索引(默認為0) port: 6379 #redis服務器連接端口 password: #redis連接密碼(默認為空) #redis連接池 jedis: pool: max-wait: -1ms #連接池最大阻塞等待時間(使用負值表示沒有限制) min-idle: 0 #連接池中的最小空閑連接 max-idle: 8 #連接池中的最大空閑連接 max-active: 8 #連接池最大連接數(使用負值表示沒有限制) timeout: 3000ms #連接超時時間(毫秒) #redis自定義配置 redis: key: prefix: authCode: "portal:authCode:" expire: authCode: 120 #mybatis映射xml文件路徑 mybatis: mapper-locations: classpath: com/zzb/test/admin/*/*.xml #jwt自定義配置 jwt: tokenHeader: Authorization #JWT存儲的請求頭 secret: mySecret #JWT加解密使用的密鑰 expiration: 604800 #JWT的超期時間(60*60*24) tokenHead: Bearer #JWT負載中拿到開頭
3、新建存放通用工具類的包utils,在utils包下新建JWT的通用工具類
JwtTokenUtil類
package com.zzb.test.admin.utils; 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 org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * JwtToken的生成工具類 * Created by zzb on 2019/11/21 10:23 */ @Component 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; /** * 封裝用戶信息,並生成token * @param userDetails * @return */ 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 this.generateTokenByClaims(claims); } /** * 根據負載生成token * @param claims * @return */ public String generateTokenByClaims(Map<String,Object> claims){ return Jwts.builder() .setClaims(claims) .setExpiration(this.generationExpirationDate()) .signWith(SignatureAlgorithm.HS512,secret) .compact(); } /** * 解析token,獲取負載主體 * @param token * @return */ public 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,獲取負載主體中的用戶名 * @param token * @return */ public String getUserNameFromToken(String token){ String username = null; try { Claims claims = this.getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { logger.info("JWT解析token失敗:{}",token); } return username; } /** * 生成token的過期時間 * @return */ public Date generationExpirationDate(){ return new Date(System.currentTimeMillis() + expiration * 1000); } /** * 驗證token是否有效 * @param token * @param userDetails * @return */ public boolean validateToken(String token, UserDetails userDetails){ String username = this.getUserNameFromToken(token); if (StringUtils.isEmpty(username)) { return false; } if (username.equals(userDetails.getUsername()) && this.isTokenExpired(token)) { return true; } return false; } /** * 驗證token是否過期 * @param token * @return */ public boolean isTokenExpired(String token){ Claims claims = this.getClaimsFromToken(token); Date expired = claims.getExpiration(); return new Date().before(expired); } /** * 刷新token * @param token * @return */ public String refreshToken(String token){ if (!this.isTokenExpired(token)) { logger.info("token已過期:{}",token); return null; } else { Claims claims = this.getClaimsFromToken(token); claims.replace(CLAIM_KEY_CREATED,new Date()); return this.generateTokenByClaims(claims); } } }
4、在通用包common下新建未登錄時返回結果類RestAuthenestAuthenticationEntryPoint
package com.zzb.test.admin.common; import cn.hutool.json.JSONUtil; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 當未登錄或者token失效訪問接口時,自定義的返回結果 * Created by zzb on 2019/11/21 14:22 */ @Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.failed(e.getMessage()))); response.getWriter().flush(); } }
在通用包common下新建無權訪問時返回結果類RestfulAccessDeinidccessDeniedHandler
package com.zzb.test.admin.common; import cn.hutool.json.JSONUtil; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 當訪問接口沒有權限時,自定義的返回結果 * Created by zzb on 2019/11/21 14:15 */ @Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.forbbiden(e.getMessage()))); response.getWriter().flush(); } }
在通用包common下新建JWT登錄授權過濾器JwtAuthenticationTokenFilter
package com.zzb.test.admin.common; import com.zzb.test.admin.utils.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.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * JWT登錄授權過濾器 * Created by zzb on 2019/11/21 14:31 */ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Autowired 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()); String username = jwtTokenUtil.getUserNameFromToken(authToken); logger.info("解析token獲取到用戶名:{}",username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken,userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); logger.info("給用戶授權:{}",username); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request,response); } }
5、在service包下新建UmsAdminService接口
package com.zzb.test.admin.service; import com.zzb.test.admin.mbg.model.UmsAdmin; import com.zzb.test.admin.mbg.model.UmsPermission; import java.util.List; /** * 用戶管理Service * Created by zzb on 2019/11/21 17:33 */ public interface UmsAdminService { /** * 根據用戶名查詢用戶 * @param username * @return */ UmsAdmin getAdminByUsername(String username); /** * 用戶注冊 * @param umsAdminParam * @return */ UmsAdmin register(UmsAdmin umsAdminParam); /** * 用戶登錄 * @param username * @param password * @return */ String login(String username,String password); /** * 根據用戶id查詢用戶權限 * @param adminId * @return */ List<UmsPermission> getPermissionList(Long adminId); }
在impl包下新建其實現類UmsAdminServiceImpl
package com.zzb.test.admin.service.impl; import cn.hutool.core.lang.Assert; import com.zzb.test.admin.dao.UmsAdminRoleRelationDao; import com.zzb.test.admin.mbg.mapper.UmsAdminMapper; import com.zzb.test.admin.mbg.model.UmsAdmin; import com.zzb.test.admin.mbg.model.UmsAdminExample; import com.zzb.test.admin.mbg.model.UmsPermission; import com.zzb.test.admin.service.UmsAdminService; import com.zzb.test.admin.utils.JwtTokenUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; 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.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.Date; import java.util.List; /** * 用戶管理Service的impl * Created by zzb on 2019/11/21 17:41 */ @Service @Transactional public class UmsAdminServiceImpl implements UmsAdminService { private static final Logger logger = LoggerFactory.getLogger(UmsAdminServiceImpl.class); @Autowired private UmsAdminMapper umsAdminMapper; @Autowired private UmsAdminRoleRelationDao umsAdminRoleRelationDao; @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override public UmsAdmin getAdminByUsername(String username) { UmsAdminExample uae = new UmsAdminExample(); uae.createCriteria().andUsernameEqualTo(username) .andDelStatusEqualTo("0") .andStatusEqualTo(true); List<UmsAdmin> admins = umsAdminMapper.selectByExample(uae); Assert.notNull(admins,"用戶名不存在"); return admins.get(0); } @Override public List<UmsPermission> getPermissionList(Long adminId) { return umsAdminRoleRelationDao.getPermissionList(adminId); } @Override public UmsAdmin register(UmsAdmin umsAdminParam) { //是否有相同用戶名 UmsAdminExample uae = new UmsAdminExample(); uae.createCriteria().andUsernameEqualTo(umsAdminParam.getUsername()) .andDelStatusEqualTo("0"); List list = umsAdminMapper.selectByExample(uae); if (!CollectionUtils.isEmpty(list)) { return null; } umsAdminParam.setStatus(true); umsAdminParam.setDelStatus("0"); umsAdminParam.setCreateTime(new Date()); umsAdminParam.setModifyTime(new Date()); //將密碼進行加密 String encodePassword = passwordEncoder.encode(umsAdminParam.getPassword()); umsAdminParam.setPassword(encodePassword); if (umsAdminMapper.insert(umsAdminParam)<1) { return null; }; return umsAdminParam; } @Override public String login(String username, String password) { String token = null; try { //校驗用戶名,封裝用戶實體 UserDetails userDetails = userDetailsService.loadUserByUsername(username); //檢驗密碼 if (!passwordEncoder.matches(password,userDetails.getPassword())) { throw new BadCredentialsException("密碼錯誤"); } //根據正確的用戶名密碼生成token UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); token = jwtTokenUtil.generateToken(userDetails); } catch (AuthenticationException e) { logger.warn("登錄異常{}",e.getMessage()); } return token; } }
需要自定義寫sql語句獲取權限,新建dao包,並在dao包下新建UmsAdminRoleRelationDao
package com.zzb.test.admin.dao; import com.zzb.test.admin.mbg.model.UmsPermission; import java.util.List; /** * 用戶與角色關系自定義dao * Created by zzb on 2019/11/21 18:00 */ public interface UmsAdminRoleRelationDao { /** * 獲取用戶的所有權限 * @param adminId * @return */ List<UmsPermission> getPermissionList(Long adminId); }
新建對應的xml文件UmsAdminRoleRelationDao.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zzb.test.admin.dao.UmsAdminRoleRelationDao"> <select id="getPermissionList" resultMap="com.zzb.test.admin.mbg.mapper.UmsPermissionMapper.BaseResultMap" parameterType="java.lang.Long"> SELECT p.* FROM ums_admin_role_relation ar LEFT JOIN ums_role r ON ar.role_id = r.id LEFT JOIN ums_role_permission_relation rp ON r.id = rp.role_id LEFT JOIN ums_permission p ON rp.permission_id = p.id WHERE ar.admin_id = #{adminId} AND p.id IS NOT NULL AND p.id NOT IN ( SELECT p.id FROM ums_admin_permission_relation pr LEFT JOIN ums_permission p ON pr.permission_id = p.id WHERE 1=1 AND pr.admin_id = #{adminId} ) UNION SELECT p.* FROM ums_admin_permission_relation pr LEFT JOIN ums_permission p ON pr.permission_id = p.id WHERE 1=1 AND pr.admin_id = #{adminId} </select> </mapper>
修改mybatis的映射配置類MybatisConfig
package com.zzb.test.admin.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; /** * Created by zzb on 2019/11/15 9:36 */ @Configuration @MapperScan(basePackages = {"com.zzb.test.admin.mbg.mapper","com.zzb.test.admin.dao"}) public class MybatisConfig { }
6、新建自定義實體包dto,並新建SpringSecurity需要的用戶實體AdminUserDetails
package com.zzb.test.admin.dto; import com.zzb.test.admin.mbg.model.UmsAdmin; import com.zzb.test.admin.mbg.model.UmsPermission; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; /** * SpringSecurity需要的用戶詳情 * Created by zzb on 2019/11/21 18:22 */ public class AdminUserDetails implements UserDetails { private UmsAdmin umsAdmin; private List<UmsPermission> permissionList; public AdminUserDetails(UmsAdmin umsAdmin,List<UmsPermission> permissionList){ this.umsAdmin = umsAdmin; this.permissionList = permissionList; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { //返回當前用戶的權限 return permissionList.stream() .filter(permission -> permission.getPerValue()!=null) .map(permission -> new SimpleGrantedAuthority(permission.getPerValue())) .collect(Collectors.toList()); } @Override public String getPassword() { return umsAdmin.getPassword(); } @Override public String getUsername() { return umsAdmin.getUsername(); } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return umsAdmin.getStatus(); } }
7、在config包下新建SpringSecurity的配置類SecturyConfig
package com.zzb.test.admin.config; import com.zzb.test.admin.common.JwtAuthenticationTokenFilter; import com.zzb.test.admin.common.RestAuthenticationEntryPoint; import com.zzb.test.admin.common.RestfulAccessDeniedHandler; import com.zzb.test.admin.dto.AdminUserDetails; import com.zzb.test.admin.mbg.model.UmsAdmin; import com.zzb.test.admin.mbg.model.UmsPermission; import com.zzb.test.admin.service.UmsAdminService; import io.jsonwebtoken.lang.Assert; 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.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.util.List; /** * SpringSecurity的配置類 * Created by zzb on 2019/11/21 13:37 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UmsAdminService umsAdminService; @Autowired private RestfulAccessDeniedHandler restfulAccessDeniedHandler; @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; /** * 配置需要攔截的url路徑、jwt過濾器及出異常后的處理器 * @param httpSecurity * @throws Exception */ @Override public void configure(HttpSecurity httpSecurity) throws Exception{ httpSecurity.csrf() .disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(HttpMethod.GET, //允許對於網站靜態資源的無授權訪問 "/", "/*.html","favicon.icon","/**/*.html","/**/*.css","/**/*.js", "/swagger-resources/**","/v2/api-docs/**") .permitAll() .antMatchers("/admin/login","/admin/register") //對登錄注冊允許匿名訪問 .permitAll() .antMatchers(HttpMethod.OPTIONS) //跨域請求會先進行一次options請求 .permitAll() // .antMatchers("/**") //測試時全部放開 // .permitAll() .anyRequest() //除上面外的所有請求全部需要鑒權認證 .authenticated(); //禁用緩存 httpSecurity.headers().cacheControl(); //添加JWT 過濾器filter httpSecurity.addFilterBefore(this.jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); //添加自定義未授權和未登錄結果返回 httpSecurity.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthenticationEntryPoint); } /** * 在用戶名和密碼校驗前添加的過濾器,如果有token信息,會自行根據token信息進行登錄 * @return * @throws Exception */ @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() throws Exception{ return new JwtAuthenticationTokenFilter(); } /** * 配置UserDetailsService 和 PasswordEncoder * @param auth * @throws Exception */ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } /** * SpringSecurty編碼比對密碼 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public UserDetailsService userDetailsService(){ //獲取登錄用戶信息 return username->{ UmsAdmin umsAdmin = umsAdminService.getAdminByUsername(username); Assert.notNull(umsAdmin,"用戶名不存在!"); List<UmsPermission> permissionList = umsAdminService.getPermissionList(umsAdmin.getId()); return new AdminUserDetails(umsAdmin,permissionList); }; } }
七、登錄與注冊功能的實現
1、在controller包下新建UmsAdminController類
package com.zzb.test.admin.controller; import cn.hutool.core.lang.Assert; import com.zzb.test.admin.common.CommonResult; import com.zzb.test.admin.mbg.model.UmsAdmin; import com.zzb.test.admin.mbg.model.UmsPermission; import com.zzb.test.admin.service.UmsAdminService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 用戶管理Controller * Created by zzb on 2019/11/22 9:37 */ @Api(tags = "UmsAdminController", description = "用戶管理") @Controller public class UmsAdminController { @Autowired private UmsAdminService umsAdminService; @Value("${jwt.tokenHead}") private String tokenHead; @Value("${jwt.tokenHeader}") private String tokenHeader; /** * 用戶注冊 * @param umsAdminParam * @return */ @ApiOperation("用戶注冊") @ResponseBody @RequestMapping(value = "/admin/register", method = RequestMethod.POST) public CommonResult register(@RequestBody UmsAdmin umsAdminParam){ String originPassword = umsAdminParam.getPassword(); //注冊用戶賬號 UmsAdmin umsAdmin = umsAdminService.register(umsAdminParam); Assert.notNull(umsAdmin,"用戶注冊失敗"); //注冊成功后自動登錄 String token = umsAdminService.login(umsAdminParam.getUsername(),originPassword); if (StringUtils.isEmpty(token)) { return CommonResult.failed("自動登錄失敗!!"); } Map<String,String> tokenMap = new HashMap<>(); tokenMap.put("token",token); tokenMap.put("tokenHead", tokenHead); return CommonResult.success(tokenMap,"自動登錄成功!!"); } /** * 用戶登錄 * @param umsAdminParam * @return */ @ApiOperation("用戶登錄") @ResponseBody @RequestMapping(value = "/admin/login", method = RequestMethod.POST) public CommonResult login(@RequestBody UmsAdmin umsAdminParam){ //根據輸入的用戶名和密碼生成token String token = umsAdminService.login(umsAdminParam.getUsername(),umsAdminParam.getPassword()); if (StringUtils.isEmpty(token)) { return CommonResult.failed("用戶名或密碼錯誤!!"); } Map<String,String> tokenMap = new HashMap<>(); tokenMap.put("token",token); tokenMap.put("tokenHead", tokenHead); return CommonResult.success(tokenMap); } /** * 獲取用戶所有的權限 * @param adminId * @return */ @ApiOperation("獲取用戶所有權限") @ResponseBody @RequestMapping(value = "/admin/getPermissionList/{adminId}", method = RequestMethod.POST) public CommonResult<List<UmsPermission>> getPermissionList(@PathVariable Long adminId){ List<UmsPermission> permissionList = umsAdminService.getPermissionList(adminId); return CommonResult.success(permissionList); } }
2、修改SwaggerConfig類,通過修改swagger-ui配置實現調用接口自帶Authorization頭,這樣就可以訪問需要登錄的接口了
package com.zzb.test.admin.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiKey; import springfox.documentation.service.AuthorizationScope; import springfox.documentation.service.SecurityReference; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * Swagger-UI的配置類 * Created by zzb on 2019/11/18 18:14 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createApi(){ return new Docket(DocumentationType.SWAGGER_2) //文檔head主體 .apiInfo(this.apiInfo()) //生成Controller的api文檔 .select() .apis(RequestHandlerSelectors.basePackage("com.zzb.test.admin.controller")) //為有@Api注解的Controller生成API文檔 // .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) //為有@ApiOperation注解的方法生成API文檔 // .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build() //添加登錄認證 .securitySchemes(this.securitySchemes()) .securityContexts(this.securityContexts()); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("Swagger-UI演示") .description("shop-test") .version("1.0.0") .build(); } private List<ApiKey> securitySchemes(){ //設置請求頭信息 List<ApiKey> result = new ArrayList<>(); ApiKey apiKey = new ApiKey("Authorization","Authorization","header"); result.add(apiKey); return result; } private List<SecurityContext> securityContexts(){ //設置需要登錄認證的路徑 List<SecurityContext> result = new ArrayList<>(); result.add(this.getContextByPath("/admin/brand/.*")); return result; } private SecurityContext getContextByPath(String pathRegex){ return SecurityContext.builder() .securityReferences(this.defaultAuth()) .forPaths(PathSelectors.regex(pathRegex)) .build(); } private List<SecurityReference> defaultAuth(){ List<SecurityReference> result = new ArrayList<>(); AuthorizationScope authorizationScope = new AuthorizationScope("global","全部通過"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; result.add(new SecurityReference("Authorization", authorizationScopes)); return result; } }
3、給PmsBrandController接口中的方法添加訪問權限
· 給查詢接口添加pms:brand:read權限
@ApiOperation("分頁獲取所有品牌") @RequestMapping(value = "/admin/brand/getList", method = RequestMethod.GET) @ResponseBody @PreAuthorize("hasAuthority('pms:brand:read')") public CommonResult<CommonPage<PmsBrand>> getList(@RequestParam(value = "pageNum", defaultValue = "1") @ApiParam("分頁頁碼") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "10") @ApiParam("每頁數量") Integer pageSize){ List<PmsBrand> list = pmsBrandService.getList(pageNum,pageSize); logger.info("分頁查詢所有品牌==》" + list); return CommonResult.success(CommonPage.restPage(list)); }
· 給修改接口添加pms:brand:update權限
@ApiOperation("添加品牌") @RequestMapping(value = "/admin/brand/insert", method = RequestMethod.POST) @ResponseBody @PreAuthorize("hasAuthority('pms:brand:create')") public CommonResult insert(@ApiParam("品牌信息") PmsBrand pmsBrand){ int count = pmsBrandService.insert(pmsBrand); logger.info("添加品牌==》" + count); if (count>0) { return CommonResult.success("添加品牌成功"); } return CommonResult.failed(); }
· 給刪除接口添加pms:brand:delete權限
@ApiOperation("刪除品牌") @RequestMapping(value = "/admin/brand/delete", method = RequestMethod.POST) @ResponseBody @PreAuthorize("hasAuthority('pms:brand:delete')") public CommonResult delete(@ApiParam("品牌id") Long id){ int count = pmsBrandService.delete(id); logger.info("刪除品牌==》" + count); if (count>0) { return CommonResult.success("刪除品牌成功"); } return CommonResult.failed(); }
· 給添加接口添加pms:brand:create權限
@ApiOperation("更新品牌") @RequestMapping(value = "/admin/brand/update", method = RequestMethod.POST) @ResponseBody @PreAuthorize("hasAuthority('pms:brand:update')") public CommonResult update(@ApiParam("修改主體") PmsBrand pmsBrand){ int count = pmsBrandService.update(pmsBrand); logger.info("更新品牌==》" + count); if (count>0) { return CommonResult.success("更新品牌成功"); } return CommonResult.failed(); }
八、測試與驗證
1、未登錄直接訪問/admin/brand/getList接口,分頁獲取所有品牌
2、注冊新用戶,訪問/admin/register接口
查看數據庫表ums_admin,有test用戶存在,id為8
3、未賦予權限,登錄后直接訪問品牌/admin/brand/getList接口,分頁獲取所有品牌
swagger登錄設置token
訪問接口
4、給test用戶賦予相應的訪問權限
配置權限表,向ums_permission表添加5條數據
給id為8的test用戶設置讀的權限
5、賦予權限后再次訪問接口/admin/brand/getList
項目github地址:https://github.com/18372561381/shoptest