package com.example.emos.wx.aop; import com.example.emos.wx.common.util.R; import com.example.emos.wx.config.shiro.ThreadLocalToken; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Aspect @Component public class TokenAspect { @Autowired private ThreadLocalToken threadLocalToken; @Pointcut("execution(public * com.example.emos.wx.controller.*.*(..))") public void aspect(){ } @Around("aspect()") public Object around(ProceedingJoinPoint point) throws Throwable{ R r=(R)point.proceed(); String token=threadLocalToken.getToken(); if(token!=null){ r.put("token",token); threadLocalToken.clear(); } return r; } }
package com.example.emos.wx.common.util; import org.apache.http.HttpStatus; import java.util.HashMap; import java.util.Map; public class R extends HashMap{ public R() { put("code", HttpStatus.SC_OK); put("msg", "success"); } public R put(String key, Object value) { super.put(key, value); return this; } public static R ok() { return new R(); } public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; } public static R ok(Map map) { R r = new R(); r.putAll(map); return r; } public static R error(int code, String msg) { R r = new R(); r.put("code", code); r.put("msg", msg); return r; } public static R error(String msg){ return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,msg); } public static R error(){ return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,"未知异常,请联系管理员"); } }
package com.example.emos.wx.config.shiro; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateUtil; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; @Component @Slf4j public class JwtUtil { @Value("${emos.jwt.secret}") private String secret; @Value("${emos.jwt.expire}") private int expire; public String createToken(int userId){ Date date = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR,5); Algorithm algorithm = Algorithm.HMAC256(secret); JWTCreator.Builder builder = JWT.create(); String token = builder.withClaim("userId",userId).withExpiresAt(date).sign(algorithm); return token; } public int getUserId(String token){ DecodedJWT jwt = JWT.decode(token); int userId = jwt.getClaim("userId").asInt(); return userId; } public void verifierToken(String token){ Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(token); } }
package com.example.emos.wx.config.shiro; import cn.hutool.core.util.StrUtil; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.TokenExpiredException; import org.apache.http.HttpStatus; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.TimeUnit; @Component @Scope("prototype") public class OAuth2Filter extends AuthenticatingFilter { @Autowired private ThreadLocalToken threadLocalToken; @Value("${emos.jwt.cache-expire}") private int cacheExpire; @Autowired private JwtUtil jwtUtil; @Autowired private RedisTemplate redisTemplate; @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest req = (HttpServletRequest) request; String token = getRequestToken(req); if(StrUtil.isBlank(token)){ return null; } return new OAuth2Token(token); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest req = (HttpServletRequest) request; if(req.getMethod().equals(RequestMethod.OPTIONS.name())){ return true; } return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; resp.setContentType("text/html"); resp.setCharacterEncoding("UTF-8"); resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin")); threadLocalToken.clear(); String token = getRequestToken(req); if(StrUtil.isBlank(token)){ resp.setStatus(HttpStatus.SC_UNAUTHORIZED); resp.getWriter().print("无效的令牌"); return false; } try{ jwtUtil.verifierToken(token); }catch (TokenExpiredException e){ if(redisTemplate.hasKey(token)){ redisTemplate.delete(token); int userId = jwtUtil.getUserId(token); token = jwtUtil.createToken(userId); redisTemplate.opsForValue().set(token,userId+"",cacheExpire, TimeUnit.DAYS); threadLocalToken.setToken(token); } else{ resp.setStatus(HttpStatus.SC_UNAUTHORIZED); resp.getWriter().print("令牌已过期"); return false; } }catch (Exception e){ resp.setStatus(HttpStatus.SC_UNAUTHORIZED); resp.getWriter().print("无效的令牌"); return false; } boolean bool = executeLogin(request,response); return bool; } @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; resp.setContentType("text/html"); resp.setCharacterEncoding("UTF-8"); resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin")); resp.setStatus(HttpStatus.SC_UNAUTHORIZED); try{ resp.getWriter().print(e.getMessage()); }catch (Exception exception){ } return false; } @Override public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; resp.setContentType("text/html"); resp.setCharacterEncoding("UTF-8"); resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin")); super.doFilterInternal(request, response, chain); } private String getRequestToken(HttpServletRequest request){ String token = request.getHeader("token"); if(StrUtil.isBlank(token)){ token = request.getParameter("token"); } return token; } }
package com.example.emos.wx.config.shiro; import com.example.emos.wx.db.pojo.TbUser; import com.example.emos.wx.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Set; @Component public class OAuth2Realm extends AuthorizingRealm { @Autowired private JwtUtil jwtUtil; @Autowired private UserService userService; @Override public boolean supports(AuthenticationToken token) { return token instanceof OAuth2Token; } /** * 授权(验证权限时调用) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) { TbUser user = (TbUser) collection.getPrimaryPrincipal(); int userId = user.getId(); SetpermsSet = userService.searchUserPermissions(userId); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(permsSet); return info; } /** * 认证(验证登录时调用) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String accessToken = (String)token.getPrincipal(); int userId = jwtUtil.getUserId(accessToken); TbUser user = userService.searchById(userId); if(user == null){ throw new LockedAccountException("账号已被锁定,请联系管理员"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,accessToken,getName()); return info; } }
package com.example.emos.wx.config.shiro; import org.apache.shiro.authc.AuthenticationToken; public class OAuth2Token implements AuthenticationToken { private String token; public OAuth2Token(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } }
package com.example.emos.wx.config.shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean("securityManager") public SecurityManager securityManager(OAuth2Realm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); securityManager.setRememberMeManager(null); return securityManager; } @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,OAuth2Filter filter){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); Mapmap = new HashMap<>(); map.put("oauth2",filter); shiroFilter.setFilters(map); Map filterMap = new LinkedHashMap<>(); filterMap.put("/webjars/**", "anon"); filterMap.put("/druid/**", "anon"); filterMap.put("/app/**", "anon"); filterMap.put("/sys/login", "anon"); filterMap.put("/swagger/**", "anon"); filterMap.put("/v2/api-docs", "anon"); filterMap.put("/swagger-ui.html", "anon"); filterMap.put("/swagger-resources/**", "anon"); filterMap.put("/captcha.jpg", "anon"); filterMap.put("/user/register", "anon"); filterMap.put("/user/login", "anon"); filterMap.put("/test/**", "anon"); filterMap.put("/meeting/recieveNotify", "anon"); filterMap.put("/**", "oauth2"); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; } @Bean("lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
package com.example.emos.wx.config.shiro; import org.springframework.stereotype.Component; @Component public class ThreadLocalToken { private ThreadLocallocal = new ThreadLocal<>(); public void setToken(String token){ local.set(token); } public String getToken(){ return local.get(); } public void clear(){ local.remove(); } }
package com.example.emos.wx.config.xss; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class XssFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request= (HttpServletRequest) servletRequest; XssHttpServletRequestWrapper wrapper=new XssHttpServletRequestWrapper(request); filterChain.doFilter(wrapper,servletResponse); } @Override public void destroy() { } }
package com.example.emos.wx.config.xss; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HtmlUtil; import cn.hutool.json.JSONUtil; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; import java.util.LinkedHashMap; import java.util.Map; public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { String value = super.getParameter(name); if(!StrUtil.hasEmpty(value)){ value = HtmlUtil.filter(value); } return value; } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if(values != null){ for (int i = 0; i < values.length; i++){ String value = values[i]; if(!StrUtil.hasEmpty(value)){ value = HtmlUtil.filter(value); } values[i] = value; } } return values; } @Override public MapgetParameterMap() { Map parameters = super.getParameterMap(); LinkedHashMap map = new LinkedHashMap(); if(parameters != null){ for (String key:parameters.keySet()){ String[] values = parameters.get(key); for (int i = 0; i < values.length; i++) { String value = values[i]; if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } values[i] = value; } map.put(key,values); } } return map; } @Override public String getHeader(String name) { String value = super.getHeader(name); if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } return value; } @Override public ServletInputStream getInputStream() throws IOException { InputStream in = super.getInputStream(); InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8")); BufferedReader buffer = new BufferedReader(reader); StringBuffer body = new StringBuffer(); String line = buffer.readLine(); while(line != null){ body.append(line); line = buffer.readLine(); } buffer.close(); reader.close(); in.close(); Map map = JSONUtil.parseObj(body.toString()); Map result = new LinkedHashMap<>(); for(String key:map.keySet()){ Object val = map.get(key); if(val instanceof String){ if(!StrUtil.hasEmpty(val.toString())){ result.put(key,HtmlUtil.filter(val.toString())); } } else { result.put(key,val); } } String json = JSONUtil.toJsonStr(result); ByteArrayInputStream bain = new ByteArrayInputStream(json.getBytes()); return new ServletInputStream() { @Override public int read() throws IOException { return bain.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
package com.example.emos.wx.config; import io.swagger.annotations.ApiOperation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiKey; import springfox.documentation.service.AuthorizationScope; import springfox.documentation.service.SecurityReference; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.ApiSelectorBuilder; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { Docket docket = new Docket(DocumentationType.SWAGGER_2); ApiInfoBuilder builder = new ApiInfoBuilder(); builder.title("EMOS在线办公系统"); ApiInfo info = builder.build(); docket.apiInfo(info); ApiSelectorBuilder selectorBuilder = docket.select(); selectorBuilder.paths(PathSelectors.any()); selectorBuilder.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)); docket = selectorBuilder.build(); ApiKey apiKey = new ApiKey("token", "token", "header"); ListapiKeyList = new ArrayList<>(); apiKeyList.add(apiKey); docket.securitySchemes(apiKeyList); AuthorizationScope scope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] scopes = {scope}; SecurityReference reference = new SecurityReference("token", scopes); List refList = new ArrayList(); refList.add(reference); SecurityContext context = SecurityContext.builder().securityReferences(refList).build(); List cxtList = new ArrayList(); cxtList.add(context); docket.securityContexts(cxtList); return docket; } }
package com.example.emos.wx.controller.form; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; @ApiModel @Data public class TestSayHelloForm { // @NotBlank // @Pattern(regexp = "^[\\u4e00-\\u9fa5]{2,15}$",message = "不符合正则表达式") @ApiModelProperty("姓名") private String name; }
package com.example.emos.wx.controller; import com.example.emos.wx.common.util.R; import com.example.emos.wx.controller.form.TestSayHelloForm; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping("/test") @Api("测试Web接口") public class TestController { @PostMapping("/sayHello") @ApiOperation("最简单的测试方法") public R sayHello(@Valid @RequestBody TestSayHelloForm form) { return R.ok().put("message", "Hello," + form.getName()); } }
package com.example.emos.wx.exception; import lombok.Data; @Data public class EmosException extends RuntimeException{ private String msg; private int code = 500; public EmosException(String msg) { super(msg); this.msg = msg; } public EmosException(String msg, Throwable e) { super(msg, e); this.msg = msg; } public EmosException(String msg, int code) { super(msg); this.msg = msg; this.code = code; } public EmosException(String msg, int code, Throwable e) { super(msg); this.msg = msg; this.code = code; } }
package com.example.emos.wx; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; @SpringBootApplication @ServletComponentScan public class EmosWxApiApplication { public static void main(String[] args) { SpringApplication.run(EmosWxApiApplication.class, args); } }
server: tomcat: uri-encoding: UTF-8 threads: max: 200 min-spare: 30 connection-timeout: 5000ms port: 8080 servlet: context-path: /emos-wx-api spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/emos?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 111111 initial-size: 8 max-active: 16 min-idle: 8 max-wait: 60000 test-while-idle: true test-on-borrow: false test-on-return: false redis: database: 0 host: localhost port: 6379 password: jedis: pool: max-active: 1000 max-wait: -1ms max-idle: 16 min-idle: 8 data: mongodb: host: localhost port: 27017 database: emos authentication-database: admin username: admin password: abc123456 mybatis: mapper-locations: classpath*:mapper/*.xml type-aliases-package: com.example.emos.wx.db.pojo configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true logging: level: root: info com.example.emos.wx.db.dao : warn pattern: console: "%d{HH:mm:ss} %-5level %msg%n" emos: jwt: secret: abc123456 expire: 5 cache-expire: 10
解决思路:在pom.xml中使用低版本的spring-boot-starter配合2.9.2版本的swagger,由原来idea创建后默认的2.5.0改为2.3.4
swagger版本
解决思路: 将swagger的版本从3.0.0改为2.9.2
学习第三章和第四章内容