目录
Spring boot 后台代码
项目目录结构
基本环境配置
修改pom文件
添加所需的jar
<!-- myBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.26</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- org.apache.commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
mybatis整合
添加jdbc.properties文件
在resources目录先创建jdbc.properties
#数据库配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springbootdemo?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 连接池配置
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
# 测试连接是否有效的sql
spring.datasource.validationQuery=select 'x'
# 建议配置为true,不影响性能,并且保证安全性
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
spring.datasource.testWhileIdle=true
# 申请连接时执行validationQuery检测连接是否有效
spring.datasource.testOnBorrow=false
# 归还连接时执行validationQuery检测连接是否有效
spring.datasource.testOnReturn=false
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
# 监控统计用的filter:stat
# 日志用的filter:log4j
# 防御sql注入的filter:wall
spring.datasource.filters=stat,log4j,wall
数据源配置
在DataSource包下创建DataSourceConfig.java
/**
* 数据源
*/
@Configuration
@MapperScan("cn.ac.sec.dao")
public class DataSourceConfig {
private static final Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
@Autowired
private JdbcConfig jdbcConfig;
@Bean
@Primary //在同样的DataSource中,首先使用被标注的DataSource
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(jdbcConfig.getUrl());
druidDataSource.setUsername(jdbcConfig.getUserName());
druidDataSource.setPassword(jdbcConfig.getPassword());
druidDataSource.setInitialSize(jdbcConfig.getInitialSize());
druidDataSource.setMinIdle(jdbcConfig.getMinIdle());
druidDataSource.setMaxActive(jdbcConfig.getMaxActive());
druidDataSource.setTimeBetweenEvictionRunsMillis(jdbcConfig.getTimeBetweenEvictionRunsMillis());
druidDataSource.setMinEvictableIdleTimeMillis(jdbcConfig.getMinEvictableIdleTimeMillis());
druidDataSource.setValidationQuery(jdbcConfig.getValidationQuery());
druidDataSource.setTestWhileIdle(jdbcConfig.isTestWhileIdle());
druidDataSource.setTestOnBorrow(jdbcConfig.isTestOnBorrow());
druidDataSource.setTestOnReturn(jdbcConfig.isTestOnReturn());
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(jdbcConfig.getMaxPoolPreparedStatementPerConnectionSize());
try {
druidDataSource.setFilters(jdbcConfig.getFilters());
} catch (SQLException e) {
if (logger.isInfoEnabled()) {
logger.info(e.getMessage(), e);
}
}
return druidDataSource;
}
/**
* Jdbc配置类
*/
@PropertySource(value = "classpath:jdbc.properties")
@Component
@Data
public static class JdbcConfig {
/**
* 数据库用户名
*/
@Value("${spring.datasource.username}")
private String userName;
/**
* 驱动名称
*/
@Value("${spring.datasource.driver-class-name}")
private String driverClass;
/**
* 数据库连接url
*/
@Value("${spring.datasource.url}")
private String url;
/**
* 数据库密码
*/
@Value("${spring.datasource.password}")
private String password;
/**
* 数据库连接池初始化大小
*/
@Value("${spring.datasource.initialSize}")
private int initialSize;
/**
* 数据库连接池最小最小连接数
*/
@Value("${spring.datasource.minIdle}")
private int minIdle;
/**
* 数据库连接池最大连接数
*/
@Value("${spring.datasource.maxActive}")
private int maxActive;
/**
* 获取连接等待超时的时间
*/
@Value("${spring.datasource.maxWait}")
private long maxWait;
/**
* 多久检测一次
*/
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
/**
* 连接在池中最小生存的时间
*/
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private long minEvictableIdleTimeMillis;
/**
* 测试连接是否有效的sql
*/
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
/**
* 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,检测连接是否有效
*/
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
/**
* 申请连接时,检测连接是否有效
*/
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
/**
* 归还连接时,检测连接是否有效
*/
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
/**
* PSCache大小
*/
@Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
private int maxPoolPreparedStatementPerConnectionSize;
/**
* 通过别名的方式配置扩展插件
*/
@Value("${spring.datasource.filters}")
private String filters;
}
}
添加mybatis-config.xml文件
在resources目录下创建mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 4.0.0以后版本可以不设置该参数 -->
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="false"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/>
<!-- 支持通过Mapper接口参数来传递分页参数 -->
<property name="supportMethodsArguments" value="false"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="none"/>
</plugin>
</plugins>
<mappers>
<mapper resource="mapper/UserDAO.xml"/>
</mappers>
</configuration>
mybatis配置
在dataSource下创建SessionFactoryConfig.java
@Configuration
@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
public class SessionFactoryConfig {
/**
* mybatis 配置路径
*/
private static String MYBATIS_CONFIG = "mybatis-config.xml";
@Autowired
private DataSource dataSource;
/***
* 创建sqlSessionFactoryBean
* 并且设置configtion 如驼峰命名.等等
* 设置mapper 映射路径
* 设置datasource数据源
*
* @Title: createSqlSessionFactoryBean
* @author: hongyangliao
* @Date: 18-1-3 上午9:52
* @param
* @return org.mybatis.spring.SqlSessionFactoryBean sqlSessionFactoryBean实例
* @throws
*/
@Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean createSqlSessionFactoryBean() throws IOException {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
// 设置mybatis configuration 扫描路径
sqlSessionFactory.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
// 设置datasource
sqlSessionFactory.setDataSource(dataSource);
return sqlSessionFactory;
}
}
在resources目录下创建mapper文件夹,内部创建对应mapper.xml
创建user对象测试
model
新建model包,添加User.java
@Data
public class User {
private String id;
private String userName;
private String password;
}
dao
新建dao包,添加UserDAO.java
public interface UserDAO {
/*
* 根据用户名获取用户对象,用于登录校验
*/
User selectUserByUserName(String userName);
}
service
新建service包,添加UserService.java
/**
*
* @ClassName: UserService
* @Description: userService接口
* @author yett
* @date Mar 22, 2019 1:35:44 PM
*/
public interface UserService {
/*
* 根据用户名获取用户对象,用于登录校验
*/
User getUserByUserName(String userName);
}
创建service接口实现类,UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDAO userDAO;
@Override
public User getUserByUserName(String userName) {
User user = userDAO.selectUserByUserName(userName);
return user;
}
}
在mapper目录下创建UserDAO.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="cn.ac.sec.dao.UserDAO" >
<resultMap id="BaseResultMap" type="cn.ac.sec.model.User" >
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="password" property="password"/>
</resultMap>
<sql id="Base_Column_List" >
id, user_name, password
</sql>
<select id="selectUserByUserName" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where user_name = #{userName}
</select>
</mapper>
创建测试controller 判断是否配置成功
@RestController
public class TestController {
@Autowired
private UserService userService;
// @GetMapping("/hello")
// public String index() {
// return "hello world!!!";
// }
@GetMapping("/getUserByUserName")
public User getUserByUserName(String userName) {
User user = userService.getUserByUserName(userName);
return user;
}
}
访问测试
浏览器访问http://localhost:8081/getUserByUserName?userName=admin,返回user对象
shiro整合
ShiroFilter工厂
自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
/**
*
* @ClassName: MySessionManager
* @Description: 自定义sessionId获取
* @author yett
* @date Mar 19, 2019 3:37:26 PM
*/
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
创建shiroConfig配置类
/**
*
* @ClassName: ShiroConfig
* @Description: Shiro配置类
* @author yett
* @date Mar 12, 2019 2:07:30 PM
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 :这是一个坑呢,一不小心代码就不好使了;
// ① authc:所有url都必须认证通过才可以访问; ② anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myRealm());
// 自定义session管理
securityManager.setSessionManager(sessionManager());
return securityManager;
}
//自定义sessionManager
@Bean
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
return mySessionManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
return myRealm;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* @return
*/
@Bean
@DependsOn({ "lifecycleBeanPostProcessor" })
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
附上自定realm
MyRealm.java
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import cn.ac.sec.model.User;
import cn.ac.sec.service.UserService;
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/*
* 授权
* @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
/*
* 认证
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = token.getPrincipal().toString();
User user = userService.getUserByUserName(userName);
if (user != null) {
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return authcInfo;
} else {
return null;
}
}
}
测试登录
创建LoginController.java
@RestController
public class LoginController {
@RequestMapping("/login")
public String login(HttpServletRequest request) {
String loginName = request.getParameter("userName");
String password = request.getParameter("password");
System.err.println(loginName+"=="+password);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(loginName,password);
if (StringUtils.isNotEmpty(loginName) && StringUtils.isNotEmpty(password)) {
try{
subject.login(token);
User user = (User)subject.getPrincipal();
subject.getSession().setAttribute("DT_LOGIN_USER", user);
}catch(Exception ex){
return "shiro 登录测试失败";
}
}
return "shiro 登录测试成功";
}
}
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>登录页</h1>
<form action="/user/login" method="post">
<!-- 1、参数必须被命名为 userName,不能为 username,保持和实体中的属性一致 -->
用户名:<input type="text" id="userName" name="userName" placeholder="userName"></input><br/>
<!-- 2、同理密码参数必须被命名为 password -->
密码:<input type="password" id="password" name="password" placeholder="password"></input><br/>
<input type="submit" value="登录"></input>
</form>
</body>
</html>
浏览器访问http://localhost:8081/login.html
成功返回