SpringBoot+shiro+Swagger实现前后分离的框架

第一步:导包

 <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>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>


        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>



        <!--下面导入数据库的使用的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <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>
        </dependency>


        <!--使用Druid这个连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.0</version>
        </dependency>



        <!--下面要导入Swagger2的相关的包-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

第二步:封装token

public class CustomToken extends UsernamePasswordToken {

    private String token;   //用户身份唯一的标识
    //这个token是在认证通过之后  用户访问其他资源的时候 来进行你给身份识别的

    public CustomToken(String token){
       this.token=token;
    }

    @Override
    public Object getPrincipal() {
        //在用户认证通过之后 再访问这个方法 默认返回的是什么?
        // Realm校验的第一个参数
        //校验我们自己写了   还有没有第一个参数这个说法?
        return token;
    }
}

3、编写过滤器

public class CustomAccessControllerFilter extends AccessControlFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        try {
            //校验身份
            //逻辑是什么?
            //第一步:获取token
            String token=request.getHeader(Constant.REQ_TOKEN);
            //第二步:看下这个token是否否为""
            if(StringUtils.isEmpty(token)){  //说明你娃身份是非法的
               throw new BusinessException(400001,"用户的请求的token不能为空");
            }else{  //说明用户带了token
                //逻辑
                //这里要将token进行封装  封装完了 交给shiro去做认证  看下身份是否合法
                CustomToken customToken = new CustomToken(token);
                //记住下面这个类 在用户第一次登陆的时候  并不会执行
                // 这个执行的时候 是在认证成功之后访问其他资源的
                //的时候 机械能给你身份校验的
                getSubject(servletRequest,servletResponse).login(customToken);
            }
        } catch (BusinessException e) {
           //如果是这个异常:返回JSON告诉前端出现问题了
           resultResponse(e.getMessageCode(),e.getDefaultMesaage(),servletResponse);
           return false;
        } catch (AuthenticationException e) {  //校验没通过的异常
            //  e.getCause() :返回的是当前异常的实例
            if(e.getCause() instanceof BusinessException){ //表示返回的是我们自定义的异常
                //将异常的实例进行转换
                BusinessException err= (BusinessException) e.getCause();
                resultResponse(err.getMessageCode(),err.getDefaultMesaage(),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.getDefaultMesaage(),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.getDefaultMesaage(),servletResponse);
            }else{  //如果执行到这里  说明 这个异常是shiro返回的
                resultResponse(500001,"系统出现了异常",servletResponse);
            }
            return false;
        }
        //当前的方法返回true才放行  否则这个程序也就执行到这里了....
        return true;
    }

    /**
     * 这个方法的主要功能就是告诉前端 一些出错的信息
     * @param messageCode
     * @param defaultMesaage
     * @param response
     */
    private void resultResponse(int messageCode, String defaultMesaage, ServletResponse response) {

        //构建返回的数据
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code",messageCode);
        jsonObject.put("msg",defaultMesaage);
        //设置下返回的数据类型
        response.setContentType(MediaType.APPLICATION_JSON_UTF8.toString());
        //获取输出流
        try {
            ServletOutputStream out = response.getOutputStream();
            //接下来项数据写出去
            out.write(jsonObject.toJSONString().getBytes());
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4、编写凭证匹配器

public class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Autowired
    private IUserService userService;

    /**
     * 下面这个方法 返回true 或者 false就决定了 这个是成功呢还是失败
     * @param token
     * @param info
     * @return
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        //  这里面要实现的功能很简单
        //  把前面传递过来的token取出来  再把存储到服务器的token取出来做比较
        // 如果一致那么就返回true  否则就返回  false
        CustomToken token1= (CustomToken) token;
        // 取出 Principal的值 (下面这个值 就是从前端传递过来进行比较的)
        String tokenVal= (String) token1.getPrincipal();
        // 从redis  或者  数据库    或者 session取出这个信息来
        //假设取出来了....
        //String tokenServer="xiaoboboxiaowangzi";
        boolean b=false;
        try{
            b = userService.tokenExistsOrNot(tokenVal);
        }catch (Exception err){
            throw new BusinessException(500001,"查询token存在失败"+err.getMessage());
        }
        //进行比较 判定前端的token和服务端的token是否一致  如果一致  那么返回true  否则返回false
        if(!b){
            throw new BusinessException(4010000,"授权信息无效请重新登录");
        }
        return true;
    }
}

5、编写realm

public class CustomRealm extends AuthorizingRealm {
    /**
     * 授权的方法
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        return simpleAuthorizationInfo;
    }

    /**
     * 认证的方法
     * 将前端放进去的token 取出来 放到校验的SimpleAuthenticationInfo中去 方便后面进行校验
     * token放到哪里呢?
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //取出前端传递过来的token
            CustomToken customToken= (CustomToken) authenticationToken;
            String token= (String) customToken.getPrincipal();

        //这里就可以取出这个token
        //在这里要将前端传递过来的token给封装到 SimpleAuthenticationInfo 对象中去
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token,token,getName());
        return simpleAuthenticationInfo;
    }
}

6、编写Swagger的配置文件

@SpringBootConfiguration
@EnableSwagger2
public class SwaggerConfig {


    @Bean
    public Docket createApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.qf.shiro.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 页面信息的配置
     * @return
     */
    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("springboot+shiro+swagger测试")
                .description("这里是整合shiro和Swagger实现前后分离")
                .version("v1.0")
                .build();
    }
}

7、编写shiro的配置文件

@SpringBootConfiguration
public class ShiroConfig {

    /**
     * 咋们项目认证(请求资源的时候 身份的认证)的realm
     * @return
     */
    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(customHashedCredentialsMatcher());
        return customRealm;
    }

    /**
     * 凭证匹配器的申明
     * @return
     */
    @Bean
    public CustomHashedCredentialsMatcher customHashedCredentialsMatcher(){
       return new CustomHashedCredentialsMatcher();
    }


    /**
     * 安全管理器
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     * 配置的是目标过滤器的代理
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean  shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //接下来配置些过滤器
        //配置自己的那个过滤器
        Map<String, Filter> maps=new LinkedHashMap<>();
        maps.put("token",new CustomAccessControllerFilter());
        shiroFilterFactoryBean.setFilters(maps);

        //对请求过滤和拦截的设置
        Map<String,String> maps1=new LinkedHashMap<>();
        //放入不拦截的页面  拦截的页面....
        maps1.put("/user/login","anon");
        //Swagger的所有请求的资源和请求的地址都不需要拦截
        maps1.put("/swagger/**","anon");
        maps1.put("/v2/api-docs","anon");
        maps1.put("/swagger-ui.html","anon");
        maps1.put("/swagger-resources/**","anon");
        maps1.put("/webjars/**","anon");
        maps1.put("/favicon.ico","anon");
        maps1.put("/captcha.jpg","anon");
        maps1.put("/csrf","anon");
        //设置我们自己的校验
        maps1.put("/**","token,authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(maps1);
        return shiroFilterFactoryBean;
    }

    /**
     * 开启aop的注解的支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        attributeSourceAdvisor.setSecurityManager(securityManager);
        return attributeSourceAdvisor;
    }
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
}

8、编写项目的配置文件

@SpringBootConfiguration
@ComponentScan(basePackages = {"com.qf.shiro"})
@MapperScan(basePackages = {"com.qf.shiro.mapper"})
public class AppConfig {

}

9、结果集的封装

9.1、接口的封装

 

public interface ResponseCodeInterface {
    /**
     * 返回的码的一个获取
     * @return
     */
    int getCode();

    /**
     * 返回消息的获取
     * @return
     */
    String getMsg();
}

9.2、返回信息码值和提示信息的封装

public enum BaseResponseCode implements ResponseCodeInterface{
    /**
     * 接下来就要和前端约束好所有的码值以及含义
     */
    SUCCESS(0,"操作成功"),
    SYSTEM_ERROR(5000001,"系统错误"),
    METHOD_INVALIDATE(4000001,"数据校验出错"),
    DATA_ERROR(4000002,"传入数据异常"),
    TOKEN_NOT_NULL(4010001,"用户认证异常");
    //整个构造函数
    BaseResponseCode(int code,String msg){
        this.code=code;
        this.msg=msg;
    }
    private int code;
    private String msg;
    @Override
    public int getCode() {
        return code;
    }
    @Override
    public String getMsg() {
        return msg;
    }
}
9.3、返回数据的封装
@Data
public class DataResult <T> {
    private int code;   //返回的码值
    private String msg; //返回的错误信息提示
    private T data;   //返回的数据
    //下面这一块是对构造器进行封装
    public DataResult(int code,T data){
       this.code=code;
       this.data=data;
       this.msg=null;
    }
    public DataResult(int code,String msg,T data){
          this.code=code;
          this.data=data;
          this.msg=msg;
    }
    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=data;
    }
    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();
    }
    /**
     * 这个很牛逼
     * 不带数据的返回
     * @param <T>
     * @return
     */
    public static <T>DataResult success(){
       return new DataResult();
    }
    /**
     * 带数据的返回
     * @param data
     * @param <T>
     * @return
     */
    public static <T>DataResult success(T data){
       return new DataResult(data);
    }
    /**
     * 自己给参数的问题
     * @param code
     * @param msg
     * @param data
     * @param <T>
     * @return
     */
    public static <T>DataResult getResult(int code,String msg,T data){
        return new <T>DataResult(code,msg,data);
    }
    /**
     * 自己给参数的问题
     * @param code
     * @param msg
     * @param <T>
     * @return
     */
    public static <T>DataResult getResult(int code,String msg){
        return new <T>DataResult(code,msg);
    }
    /**
     * 直接传递一个枚举进来
     * @param baseResponseCode
     * @param <T>
     * @return
     */
    public static <T>DataResult getResult(BaseResponseCode baseResponseCode){
        return new <T>DataResult(baseResponseCode);
    }
    /**
     * 直接传递一个枚举进来
     * @param baseResponseCode
     * @param <T>
     * @return
     */
    public static <T>DataResult getResult(BaseResponseCode baseResponseCode,T data){
        return new <T>DataResult(baseResponseCode,data);
    }
}

10、controller的编写

@RestController
@Api(tags = {"用户接口"})
public class UserController {
    @Autowired
    private IUserService userService;
    private Logger logger= LoggerFactory.getLogger(UserController.class);
    /**
     * 登陆的接口
     * @param user
     * @return
     */
    @RequestMapping(value = "/user/login",method = RequestMethod.POST)
    @ApiOperation(value = "用户登陆的接口")
    public DataResult<User> login(@RequestBody User user){
       //这个里面应该干什么?
        /**
         * 说白了 调用业务逻辑层的方法
         *  异常的捕获
         *  返回数据
         */
        DataResult<User> dataResult=null;
        try {
            User user1 = userService.login(user);
            dataResult=DataResult.success(user1);
        } catch (Exception e) {
            if(e instanceof BusinessException){  //说明是业务异常
                BusinessException err= (BusinessException) e;
                //应该干什么?
                dataResult=new DataResult<>(err.getMessageCode(),err.getDefaultMesaage());
            }else{
                //dataResult=new DataResult<>(500001,"系统异常造成登陆失败");
                dataResult=DataResult.getResult(BaseResponseCode.SYSTEM_ERROR.getCode(),BaseResponseCode.SYSTEM_ERROR.getMsg());
            }
            return dataResult;
        }
        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;
       try{
           //返回用户数据
           List<User> users = userService.findUserList();
           userLists=DataResult.success(users);
           logger.info("获取数据成功....");
       }catch (Exception err){
           //说明获取信息失败了
           logger.error("获取用户信息失败:"+err.fillInStackTrace());
           userLists=DataResult.getResult(BaseResponseCode.SYSTEM_ERROR.getCode(),BaseResponseCode.SYSTEM_ERROR.getMsg());
       }
       return userLists;
   }
}

11、Service的编写

11.1、Service接口的编写

public interface IUserService {
    /**
     * 登陆
     * @param user
     * @return
     */
       User login(User user);

    /**
     * 通过名字找到用户
     * @param userName
     * @return
     */
       User findUserByName(String userName);

     /**
       * 更新token到数据库
      * @param user
      */
      void updateToken(User user);

    /**
     * 查询所有的用户
     * @return
     */
     List<User> findUserList()throws Exception;

    /**
     * 判定这个token是否存在
     * @param token
     * @return
     */
     boolean tokenExistsOrNot(String token);

}

11.2、Service实现的编写

@Service
@Transactional
public class UserService implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public User login(User user){
        //这个类里面应该干什么?
        /**第一步:获取到前端传递过来的用户名
         *第二步:通过用户名 获取用户对象
         * 第三步:校验
         * 第四步:生成token保存到数据库
         * 第五步:将token封装到返回数据里面给前端
         */
        //获取用户名
        String userName = user.getUserName();
        //通过用户名 找用户名找对象
        User userResult = findUserByName(userName);
        //第三步:校验
        if(null==userResult){  //说明用户名不对
            throw new BusinessException(40001,"用户名不对");
        }
        //说明:用户名是对的
        //比较密码
        if(!(userResult.getPassword().equals(user.getPassword()))){
            throw new BusinessException(40002,"密码不对");
        }
        //执行到这里说明用户身份合法的
        //先将数据保存到一个类里面
        //首先要生成token这个值
        String token= UUID.randomUUID().toString();
        Date date=new Date();
        //设置这个值给user对象
        userResult.setToken(token);
        userResult.setExpireDate(date);
        //下面就是更新这个数据库的数据
        updateToken(userResult);
        //将这个信息返回给前端
        //一般情况下 密码是不需要返回的
        userResult.setPassword("");
        //设置返回数据的对象
        //DataResult<User> userDataResult = new DataResult<>(0,"认证成功",userResult);
        return userResult;
    }
    @Override
    public User findUserByName(String userName) {
        return userMapper.findUserByName(userName);
    }
    @Override
    public void updateToken(User user) {
        userMapper.updateToken(user);
    }
    @Override
    public List<User> findUserList() throws Exception{
        List<User> userList = userMapper.findUserList();
        //接下来对数据进行封装
        DataResult<List<User>> dataResult = new DataResult<>(0, "请求完美", userList);
        return userList;
    }
    @Override
    public boolean tokenExistsOrNot(String token) {
        //通过token查询用户信息
        User userResult = userMapper.findUserByToken(token);
        //接下来就要判断了
        if(null!=userResult){
           return true;
        }
        return false;
    }
}

13、Mapper接口的编写

public interface UserMapper {
    /**
     * 通过名字找到用户
     * @param userName
     * @return
     */
    User findUserByName(String userName);
    /**
     * 更新token到数据库
     * @param user
     */
    void updateToken(User user);
    /**
     * 查询所有的用户
     * @return
     */
    List<User> findUserList();
    /**
     * 查看当前的token是否在数据库中存在
     * @param token
     * @return
     */
    User findUserByToken(String token);
}

14、mapper.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" parameterType="string" resultType="user">
        select * from t_user where userName=#{value}
    </select>
    <!--更新数据库用户的token-->
    <update id="updateToken" parameterType="user">
        update t_user set token=#{token} where id=#{id}
    </update>
    <!--查找所有的用户-->
    <select id="findUserList" resultType="user">
        select * from t_user
    </select>
    
    <!--查看token是否存在-->
    <select id="findUserByToken" parameterType="String" resultType="user">
        select * from t_user where token=#{value}
    </select>
</mapper>

15、用户对象的编写

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -5199670739008077879L;
    private Integer id;
    private String userName;
    private String password;
    private String token;
    private Date expireDate;  //token的过期时间
}

17、properties文件的编写

mybatis.type-aliases-package=com.qf.shiro.pojo
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
spring.datasource.username=root
spring.datasource.password=root

18、自定义异常的编写

public class BusinessException extends RuntimeException{
    private int messageCode;
    private String defaultMesaage;
    public BusinessException(int messageCode,String defaultMesaage){
          super(defaultMesaage);
          this.messageCode=messageCode;
          this.defaultMesaage=defaultMesaage;
    }

    public String getDefaultMesaage() {
        return defaultMesaage;
    }

    public int getMessageCode() {
        return messageCode;
    }
}

猜你喜欢

转载自www.cnblogs.com/yindong2019/p/12407663.html