SpringBoot整合shiro和Swagger2实现前后分离
2020-03-08 13:10
901 查看
项目结构:
1、导包
<dependencies> <!--下面导入数据库的使用的包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!--使用Druid这个连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.0</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!--shiro整合spring--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <!--Swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </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> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
2、编写application.properties文件
#别名 mybatis.type-aliases-package=com.qf.shiro.pojo #mapper映射 mybatis.mapper-locations=classpath:mapper/*.xml #连接池类型 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #数据源 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql:///nz1904?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123
3、实体
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -8625913614761133825L; private Integer id; private String username; private String password; private String token; /** * token的过期时间 */ private Date expireDate; }
4、mapper接口
public interface UserMapper { /** * 通过名字查询用户 * @return */ User findUserByName(String username); /** * 更新token到数据库 * @param user */ void updateToken(User user); /** * 查询所有的用户 * @return */ List<User> findUserList() throws Exception; /** * 查询数据库token是否存在 * @param token * @return */ User findUserByToken(String token); }
5、UserMapper.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.qf.shiro.mapper.UserMapper"> <!--通过名字查询用户--> <select id="findUserByName" resultType="user"> select * from user where username=#{username} </select> <!--更新token到数据库--> <select id="updateToken"> update user set token =#{token} where id=#{id} </select> <!--查询所有的用户--> <select id="findUserList" resultType="user"> select * from user; </select> <!--查询数据库token是否存在--> <select id="findUserByToken" resultType="user"> select * from user where token=#{token} </select> </mapper>
6、Service实现类
@Service @Transactional public class UserService implements IUserService { @Autowired private UserMapper userMapper; /** * 登录认证 * 1.获取前端传递的用户名 * 2.通过用户名获取用户对象 * 3.校验 * 4.生成token,保存到数据库 * 5.将token封装在返回数据里面返回给前端 * @param user * @return */ @Override public DataResult<User> login(User user) { //1.获取用户名 String username = user.getUsername(); //2.通过用户名从数据库查询用户对象 User userResult = findUserByName(username); //3.校验 if(null==userResult){ //非空判断 throw new BusinessException(400001,"用户名不对"); } //比较密码 if(!(userResult.getPassword().equals(user.getPassword()))){ throw new BusinessException(400002,"密码不对"); } //执行到这里说明身份是合法的 //生成token String token = UUID.randomUUID().toString(); Date date = new Date(); userResult.setToken(token); userResult.setExpireDate(date); //更新到数据库 updateToken(userResult); //将信息返回给前端,密码不需要返回 userResult.setPassword(""); //设置返回数据的对象 DataResult<User> userDataResult = new DataResult<User>(0,"认证成功",userResult); return userDataResult; } @Override public User findUserByName(String username) { return userMapper.findUserByName(username); } @Override public void updateToken(User user) { userMapper.updateToken(user); } @Override public DataResult<List<User>> findUserList() throws Exception{ List<User> userList = userMapper.findUserList(); //对数据进行封装 DataResult<List<User>> dataResult = new DataResult<>(0,"请求完美",userList); return dataResult; } @Override public boolean tokenExistsOrNot(String token) { //根据token查询用户 User userResult = userMapper.findUserByToken(token); if(null!=userResult){ return true; } return false; } }
7、编写realm
public class CustomRealm extends AuthorizingRealm { /** * 授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); return simpleAuthorizationInfo; } /** * 认证 * 取出前端传递过来的token,并将其封装在simpleAuthenticationInfo往后传递 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //取出token CustomToken customToken = (CustomToken) authenticationToken; String token = (String) customToken.getPrincipal(); //第一个参数放token,第二个随便 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token,token,getName()); return simpleAuthenticationInfo; } }
8、自定义token
public class CustomToken extends UsernamePasswordToken { /** * 用户身份唯一的标识 */ private String token; public CustomToken(String token){ this.token = token; } @Override public Object getPrincipal() { return token; } }
9、全局异常
public class BusinessException extends RuntimeException{ /** * 状态码 */ private int messageCode; /** * 异常信息 */ private String defaultMessage; public BusinessException(int messageCode,String defaultMessage){ super(defaultMessage); this.defaultMessage = defaultMessage; this.messageCode = messageCode; } public int getMessageCode() { return messageCode; } public String getDefaultMessage() { return defaultMessage; } }
9、编写静态常类
public class Constant { public static final String REQ_TOKEN = "token"; }
10、Swagger2配置
@SpringBootConfiguration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.qf.shiro.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("SpringBoot-Swagger2-Shiro测试") .description("实现前后分离") .version("v1.0") .build(); } }
11、shiro的配置
@SpringBootConfiguration public class ShiroConfig { /** * 资源请求的认证(非登录) * @return */ @Bean public CustomRealm customRealm(){ CustomRealm customRealm = new CustomRealm(); customRealm.setCredentialsMatcher(customHashedCredentialsMatcher()); return customRealm; } /** * 凭证匹配器的申明 * @return */ @Bean public CustomHashedCredentialsMatcher customHashedCredentialsMatcher(){ return new CustomHashedCredentialsMatcher(); } /** * securityManager配置 * @return */ @Bean public DefaultWebSecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm()); return securityManager; } /** * 配置目标过滤器的代理 * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //配置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //配置过滤器 Map<String, Filter> map = new HashMap<>(); map.put("token",new CustomAccessControlFilter()); shiroFilterFactoryBean.setFilters(map); //对请求资源的过滤和拦截 Map<String,String> maps = new LinkedHashMap<>(); maps.put("/user/login","anon"); //Swagger2的所有请求都不需要拦截 maps.put("/swagger/**","anon"); maps.put("/v2/api-docs","anon"); maps.put("/swagger-ui.html","anon"); maps.put("/swagger-resources/**","anon"); maps.put("/webjars/**","anon"); maps.put("/favicon.ico","anon"); maps.put("/captcha.jpg","anon"); maps.put("/csrf","anon"); //设置我们自己的校验 maps.put("/**","token,authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(maps); return shiroFilterFactoryBean; } /** * aop对注解的支持 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } }
12、过滤器的编写
public class CustomAccessControlFilter extends AccessControlFilter { @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { //返回false才执行下面的方法 return false; } @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { HttpServletRequest request = (HttpServletRequest) servletRequest; try { //1.获取token String token = request.getHeader(Constant.REQ_TOKEN); //2.判断token是否为"" if(StringUtils.isEmpty(token)){ //说明身份是非法的 throw new BusinessException(400001,"用户的请求token不能为空"); } else{ //用户信息携带了token //这里将token进行封装并交给shiro去认证,判断是否token合法 CustomToken customToken = new CustomToken(token); //在用户第一次登录的时候并不执行,在认证成功之后访问其他资源的时候才会执行 getSubject(servletRequest,servletResponse).login(customToken); } } catch (BusinessException e) { //出现该异常,返回JSON告诉前端出现问题了 resultResponse(e.getMessageCode(),e.getDefaultMessage(),servletResponse); return false; } catch (AuthenticationException e) { //e.getCause()返回的是当前异常的实例 if(e.getCause() instanceof BusinessException){ //返回的是自定义的异常 //将异常的实例进行转换 BusinessException err = (BusinessException) e.getCause(); resultResponse(err.getMessageCode(),err.getDefaultMessage(),servletResponse); } else { //说明该异常是shiro返回的 resultResponse(400001,"用户认证失败",servletResponse); } return false; } catch (AuthorizationException e){ //e.getCause()返回的是当前异常的实例 if(e.getCause() instanceof BusinessException){ //返回的是自定义的异常 //将异常的实例进行转换 BusinessException err = (BusinessException) e.getCause(); resultResponse(err.getMessageCode(),err.getDefaultMessage(),servletResponse); } else { //说明该异常是shiro返回的 resultResponse(403001,"用户没有访问权限",servletResponse); } return false; } catch (Exception e){ //一些未考虑的异常 //e.getCause()返回的是当前异常的实例 if(e.getCause() instanceof BusinessException){ //返回的是自定义的异常 //将异常的实例进行转换 BusinessException err = (BusinessException) e.getCause(); resultResponse(err.getMessageCode(),err.getDefaultMessage(),servletResponse); } else { //说明该异常是shiro返回的 resultResponse(500001,"系统出现了异常",servletResponse); } return false; } //返回true才放行 return true; } /** * 告诉前端一些出错的信息 * @param messageCode * @param defaultMessage * @param response */ private void resultResponse(int messageCode, String defaultMessage, ServletResponse response) { //构建返回的数据 JSONObject jsonObject = new JSONObject(); jsonObject.put("code",messageCode); jsonObject.put("msg",defaultMessage); //设置返回的数据类型 response.setContentType(MediaType.APPLICATION_JSON_UTF8.toString()); //获取输出流 try { ServletOutputStream out = response.getOutputStream(); //将数据写出去 out.write(jsonObject.toJSONString().getBytes()); out.flush(); } catch (IOException e) { e.printStackTrace(); } } }
13、重写凭证校验器
public class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher { @Autowired private IUserService userService; /** * 根据返回的true or false 决定认证成功还是失败 * 把前面传递过来的token和数据库的token做比较,一致返回true * @param token * @param info * @return */ @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { CustomToken customToken = (CustomToken) token; //从前端传递过来进行比较 String tokenVal = (String) customToken.getPrincipal(); //从数据库取出token // String tokenServer = "qishidikaxi"; boolean b = false; try { b = userService.tokenExistsOrNot(tokenVal); } catch (Exception e) { throw new BusinessException(500001,"查询token存在失败"+e.getMessage()); } //将两者进行比较 if(!b){ throw new BusinessException(401000,"授权信息无效请重新登录"); } return true; } }
14、controller
@RestController @Api(tags = {"用户接口"}) public class UserController { private Logger logger = LoggerFactory.getLogger(UserController.class); @Autowired private IUserService userService; /** * 登录的接口 * 1.调用业务逻辑层的方法 * 2.异常的捕获 * 3.返回数据 * @return */ @RequestMapping(value = "/user/login",method = RequestMethod.POST) @ApiOperation(value = "用户登录接口") public DataResult<User> login(@RequestBody User user){ DataResult<User> dataResult = null; try { dataResult = userService.login(user); } catch (Exception e) { if(e instanceof BusinessException){ //说明是业务异常 BusinessException err = (BusinessException) e; dataResult = new DataResult<>(err.getMessageCode(),err.getDefaultMessage()); } else { // dataResult = new DataResult<>(500001,"系统异常登录失败"); dataResult = DataResult.getResult(BaseResponseCode.SYSTEM_ERROR.getCode(),BaseResponseCode.SYSTEM_ERROR.getMsg()); } } return dataResult; } /** * 查找所有的用户 * @return */ @RequestMapping(value = "/user/list",method = RequestMethod.GET) @ApiOperation(value = "获取所有的用户信息") @ApiImplicitParam(paramType = "header",name="token",value = "用户token",required = true,dataType = "String") public DataResult<List<User>> findUserList(){ //定义返回数据 DataResult<List<User>> userLists = new DataResult<>(); try { //返回用户数据 userLists = userService.findUserList(); logger.info("获取用户信息成功"); } catch (Exception err) { logger.error("获取用户信息失败"+err.fillInStackTrace()); userLists = new DataResult<List<User>>(400300,"获取用户信息失败:"+err.getMessage(),null); } return userLists; } }
15、响应值的接口
/** * @Description:返回码的接口 * @Author: xbb * @Date:2020/3/2 */ public interface ResponseCodeInterface { /** * 返回码的获取 * @return */ int getCode(); /** * 返回信息的获取 * @return */ String getMsg(); }
16、统一错误信息
public enum BaseResponseCode implements ResponseCodeInterface { /** * 和前端约束好所有的码值以及含义 */ SUCCESS(0,"操作成功"), SYSTEM_ERROR(500001,"系统错误"), METHOD_INVALIDATE(400001,"数据校验错误"), DATA_ERROR(400002,"传入数据异常"), TOKEN_NOT_NULL(401001,"用户认证异常") ; private Integer code; private String msg; BaseResponseCode(Integer code,String msg){ this.code = code; this.msg = msg; } @Override public int getCode() { return code; } @Override public String getMsg() { return msg; } }
17、响应前端数据的封装
@Data public class DataResult<T> { private Integer code; private String msg; private T data; /*构造器的封装*/ public DataResult(Integer code,T data){ this.code = code; this.msg = null; this.data = data; } public DataResult(int code,String msg,T data){ this.code = code; this.msg = msg; this.data = data; } public DataResult(int code,String msg){ this.code = code; this.msg = msg; } public DataResult(){ this.code = BaseResponseCode.SUCCESS.getCode(); this.msg = BaseResponseCode.SUCCESS.getMsg(); this.data = null; } public DataResult(T data){ this.code = BaseResponseCode.SUCCESS.getCode(); this.msg = BaseResponseCode.SUCCESS.getMsg(); this.data = null; } public DataResult(ResponseCodeInterface responseCodeInterface){ this.data = null; this.code = responseCodeInterface.getCode(); this.msg = responseCodeInterface.getMsg(); } public DataResult(ResponseCodeInterface responseCodeInterface,T data){ this.data = data; this.code = responseCodeInterface.getCode(); this.msg = responseCodeInterface.getMsg(); } /*响应成功的封装*/ public static <T>DataResult success(){ return new DataResult(); } public static <T>DataResult success(T data){ return new DataResult(data); } public static <T>DataResult getResult(int code,String msg,T data){ return new <T>DataResult(code,msg,data); } /*获取结果的封装*/ public static <T>DataResult getResult(int code,String msg){ return new <T>DataResult(code,msg); } public static <T>DataResult getResult(BaseResponseCode baseResponseCode){ return new <T>DataResult(baseResponseCode); } public static <T>DataResult getResult(BaseResponseCode baseResponseCode,T data){ return new <T>DataResult(baseResponseCode,data); } }
18、测试
登录测试
获取用户信息测试
- 点赞 1
- 收藏
- 分享
- 文章举报
相关文章推荐
- spring boot 前后端分离整合shiro(五)整合redis并实现并发登录控制
- 以后直接拿着用:SpringBoot+shiro+Swagger实现前后分离的框架
- springboot整合shiro-spring-boot-web-starter实现前后端分离的跨域问题
- Spring boot整合shiro+jwt实现前后端分离
- SpringBoot整合Shiro实现动态权限加载更新+Session共享+单点登录
- 前后端分离之SpringBoot2.x整合mybatis实现数据库的增删改查操作(一)
- spring boot 前后端分离整合shiro(三)ShiroConfig
- spring boot 整合swagger2 实现动态生成接口文档
- Shiro+Springboot+Vue前后端分离实现权限管理
- spring boot整合Shiro实现单点登录
- 前后端分离之SpringBoot2.x整合mybatis实现数据库的增删改查操作(二)
- spring boot整合redis实现shiro的分布式session共享的方法
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)
- SpringBoot 2.0整合阿里云OSS,实现动静分离架构
- Spring boot 整合shiro 实现登陆验证
- Spring Boot + Vue + Shiro 实现前后端分离、权限控制
- Spring boot整合redis实现shiro的分布式session共享
- SpringBoot2.0整合Shiro框架实现用户权限管理的示例
- Springboot html vue.js 前后分离 跨域 Activiti6 工作流 集成代码生成器 shiro 权限
- springboot项目整合shiro实现权限管理