您的位置:首页 > 编程语言 > Java开发

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
  • 收藏
  • 分享
  • 文章举报
X_Q_B_J 发布了13 篇原创文章 · 获赞 6 · 访问量 1769 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: