SpringBoot整合shiro和Swagger2实现前后分离

项目结构:

在这里插入图片描述

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、测试

  登录测试
在这里插入图片描述
获取用户信息测试
在这里插入图片描述

发布了11 篇原创文章 · 获赞 6 · 访问量 188

猜你喜欢

转载自blog.csdn.net/X_Q_B_J/article/details/104621825
今日推荐