SpringBoot+shiro+Swagger2实现前后分离
0
思路:
代码下载:https://download.csdn.net/download/wmlwml0000/12207945
博客说明:步骤为核心代码,非完整代码。 表结构参照实体类 自建即可。
1、导包
<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>
2、封装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的过期时间
}
16、常量类的编写
public class Constant {
public static final String REQ_TOKEN="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;
}
}