Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
重点就一个gav:spring-boot-starter-web,其他可以删除。
该文件默认为空,springboot的默认启动端口号:8080,可以在改文件修改。建议用yml的格式
server:
port: 8080
public class JxcApplication {
public static void main(String[] args) {
SpringApplication.run(JxcApplication.class, args);
}
}
在项目包路径下创建一个Controller,写一个HelloController
@Controller
public class HelloController {
@RequestMapping("/")
@ResponseBody
public String getHello() {
return "hello";
}
}
浏览器查看效果
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
附上properties
<properties>
<project.version>1.0</project.version>
<java.version>1.8</java.version>
<mysql.version>5.1.25</mysql.version>
<pagehelper.version>1.2.12</pagehelper.version>
<jwt.version>0.9.1</jwt.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<commons.lang.version>3.9</commons.lang.version>
<aspectjweaver.version>1.9.4</aspectjweaver.version>
<fastjson.version>1.2.62</fastjson.version>
</properties>
配置端口,项目根路径,spring配置,mybatis配置,分页插件配置
server:
port: 8100
servlet:
context-path: /api
spring:
profiles:
active: dev
http:
encoding:
charset: UTF-8
force: true
enabled: true
mybatis:
mapper-locations: classpath:/mapper/*.xml
type-aliases-package: com.example.jxc.domain.entity.*
configuration:
cache-enabled: true
lazy-loading-enabled: true
multiple-result-sets-enabled: true
use-column-label: true
call-setters-on-nulls: true
local-cache-scope: session
map-underscore-to-camel-case: true
default-executor-type: BATCH
auto-mapping-behavior: PARTIAL
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
配置数据库信息,通过application.yml
中的active来启用dev配置文件
spring:
profiles:
active: dev
application-dev.yml
完整配置
spring:
datasource:
# 数据源基本配置
username: root
password:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/fhshgl
type: com.alibaba.druid.pool.DruidDataSource
# 数据源其他配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){
return new DruidDataSource();
}
/**
* 配置Druid的监控
* @return
*/
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
//默认就是允许所有访问
initParams.put("allow","");
initParams.put("deny","192.168.15.21");
bean.setInitParameters(initParams);
return bean;
}
/**
* 配置一个web监控的filter
* @return
*/
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
public class MyRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//拿到封装好账户密码的token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
//用户校验
User user = this.userService.getUser(userName);
if (user == null) {
throw new AuthenticationException("用户名或密码错误!");
}
//加盐 计算盐值 保证每个加密后的 MD5 不一样
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt,
this.getName());
return info;
}
}
@Configuration
public class ShiroConfig {
/**
* 主要配置一些相应的URL的规则和访问权限
*/
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/system/logout", "anon");
//过滤链定义,从上向下顺序执行,一般将/**放在最为下边
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
// filterChainDefinitionMap.put("/static/**", "anon");
shiroFilterFactoryBean.setLoginUrl("/system/login");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定义身份认证 realm;
* <p>
* 必须写这个类,并加上 @Bean 注解,目的是注入 MyRealm,
* 否则会影响 MyRealm 中其他类的依赖注入
*/
@Bean
public MyRealm customRealm() {
return new MyRealm();
}
/**
* 开启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;
}
/**
* Shiro生命周期处理器 ---可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
public class CostFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String origin = req.getHeader("Origin");
if (origin == null) {
origin = req.getHeader("Referer");
}
// 允许指定域访问跨域资源
resp.setHeader("Access-Control-Allow-Origin", origin);
// 允许客户端携带跨域cookie,此时origin值不能为“*”,只能为指定单一域名
resp.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equals(req.getMethod())) {
String allowMethod = req.getHeader("Access-Control-Request-Method");
String allowHeaders = req.getHeader("Access-Control-Request-Headers");
// 浏览器缓存预检请求结果时间,单位:秒
resp.setHeader("Access-Control-Max-Age", "86400");
// 允许浏览器在预检请求成功之后发送的实际请求方法名
resp.setHeader("Access-Control-Allow-Methods", allowMethod);
// 允许浏览器发送的请求消息头
resp.setHeader("Access-Control-Allow-Headers", allowHeaders);
resp.setHeader("Content-Type", "application/json;charset=utf-8");
return;
}
chain.doFilter(request, response);
}
}
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean configureFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean<>();
bean.setName("costFilter");
CostFilter costFilter = new CostFilter();
bean.setFilter(costFilter);
bean.setOrder(1);
List<String> urlList = new ArrayList<String>();
urlList.add("/*");
bean.setUrlPatterns(urlList);
return bean;
}
}
jwt工具类
public class JwtUtils {
public static SecretKey getBase64Key() {
String stringKey = "MyJwtSecret";
byte[] encodeKey = Base64.getDecoder().decode(stringKey);
SecretKey key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
return key;
}
/**
* 签发token
*
* @param userName 用户名
* @return token
*/
public static String create(String userName) {
Date now = new Date(System.currentTimeMillis());
String token = Jwts.builder()
.setIssuedAt(now)
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.signWith(SignatureAlgorithm.HS256, getBase64Key())
.compact();
return token;
}
/**
* 解析token
*
* @param token token
* @return 用户名
*/
public static String parse(String token) {
String username = null;
try {
username = Jwts.parser()
.setSigningKey(getBase64Key())
.parseClaimsJws(token.replace("Bearer ", ""))
.getBody()
.getSubject();
} catch (Exception e) {
e.printStackTrace();
}
return username;
}
/**
* 检验token是否过期
*
* @param token
* @return
*/
public static boolean verify(String token) {
Date expiraDate = null;
Date currentDate = new Date();
try {
expiraDate = Jwts.parser()
.setSigningKey(getBase64Key())
.parseClaimsJws(token.replace("Bearer ", ""))
.getBody()
.getExpiration();
if (currentDate.before(expiraDate)) {
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
}
@Component
public class TokenInterceptor implements HandlerInterceptor {
public Log log = LogFactory.getLog(TokenInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
response.setCharacterEncoding("utf-8");
String token = request.getHeader("Authorization");
if (token != null) {
boolean result = JwtUtils.verify(token);
if (result) {
return true;
}
}
log.error("认证失败");
response.setStatus(HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
return false;
}
}
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/**/login")
.excludePathPatterns("/**/logOut");
}
}
@RestController
@RequestMapping("/system")
public class LoginController extends BaseController {
@Autowired
private UserService userService;
/**
* 浏览器点击登录
*
* @param user
* @return
*/
@PostMapping("/login")
public R login(@RequestBody User user) {
log.debug("------浏览器点击登录------");
String userName = user.getUsername();
String passWord = user.getPassword();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, MD5.md5Salt(passWord, userName));
Subject subject = SecurityUtils.getSubject();
try {
subject.login(usernamePasswordToken);
String token = JwtUtils.create(userName);
return R.ok(R.SUCCESS, R.MSG_SUCCESS, token);
} catch (AuthenticationException e) {
e.printStackTrace();
return R.error(R.MSG_LOGIN_ERROR);
}
}
}