电商项目实战(架构四)——SpringSecurity + JWT实现认证与授权进行用户登录
一、前言
登录和授权模块是整个项目的安全锁,是开发中的必要一环。本文通过使用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