Yunqi information:[ Click to see more industry information]
Here you can find the first-hand cloud information of different industries. What are you waiting for? Come on!
This paper summarizes the reconstruction of the project from the practice. The original project uses the Springboot+freemarker template. During the development process, the front-end logic is disgusting. The back-end Controller layer must also return the Freemarker template Model andview gradually has the idea of front and back end separation. Since it has not been contacted before, it mainly refers to some blog tutorials on the Internet, etc., and has preliminarily completed the front and back end separation, which is recorded here for reference.
1, Separation of front and back
The front-end is separated from the back-end to form a front-end project. The front-end only uses Json to interact with the back-end. The back-end does not return the page, but only returns the Json data. The front and back end are completely through the public API convention.
2, Backend Springboot
Spring boot doesn't go into details any more. The Controller layer returns Json data.
@RequestMapping(value = "/add", method = RequestMethod.POST) @ResponseBody public JSONResult addClient(@RequestBody String param) { JSONObject jsonObject = JSON.parseObject(param); String task = jsonObject.getString("task"); List<Object> list = jsonObject.getJSONArray("attributes"); List<String> attrList = new LinkedList(list); Client client = JSON.parseObject(jsonObject.getJSONObject("client").toJSONString(),new TypeReference<Client>(){}); clientService.addClient(client, task, attrList); return JSONResult.ok(); }
Post requests are received using the @ RequestBody parameter.
3, Front end Vue + ElementUI + Vue router + Vuex + axios + webpack
Main reference:
https://cn.vuejs.org/v2/guide/
https://github.com/PanJiaChen/vue-admin-template/blob/master/README-zh.md
https://github.com/PanJiaChen/vue-element-admin
Here we mainly talk about the problems encountered in the development project:
1. Cross domain
Because the front-end project in the development uses webpack to start a service, the front-end and the back-end are not under the same port, which inevitably involves cross domain:
XMLHttpRequest will comply with the same origin policy. That is to say, scripts can only access resources of the same protocol / host name / port. If you want to break this limitation, it is the so-called cross domain. At this time, you need to comply with the cross origin resource sharing mechanism.
There are two solutions to cross domains:
1. The server side is developed by itself, so an interceptor can be added at the back end
@Component public class CommonIntercepter implements HandlerInterceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //Cross domain allowed, not in postHandle response.setHeader("Access-Control-Allow-Origin", "*"); if (request.getMethod().equals("OPTIONS")) { response.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH"); response.addHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization"); } return true; } }
response.setHeader("Access-Control-Allow-Origin", "*");
It is mainly to add "access control allow origin: *" in Response Header
if (request.getMethod().equals("OPTIONS")) { response.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH"); response.addHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization"); }
Due to the integration of shiro in the front-end and back-end separation, we need to customize a 'Authorization' field in the headers. At this time, ordinary requests such as GET and POST will become preflight request, that is, an OPTIONS request will be sent in advance before the GET and POST requests, which will be discussed later. Recommend a blog about preflight request.
https://blog.csdn.net/cc1314_/article/details/78272329
2. The server is not developed by itself. You can add proxyTable in the front end.
However, this can only be used during development. For subsequent deployment, the front-end project can be put on the back-end as a static resource, so there is no cross domain (because of the needs of the project, I do this now. According to the introduction of online blog, nginx can be used, and how to do it can be searched on the Internet).
Encountered a lot of people on the Internet said that no matter how the proxyTable is modified, it has no effect.
1. (very important) make sure that the address of the proxyTable configuration can be accessed, because if it cannot be accessed, you will still see the prompt 404 when debugging the browser F12.
And notice that the domain name that js prompts for error in F12 is the domain name written by js, not the domain name after proxy. (l the landlord has encountered this problem. The back-end address lacks query parameters, and the proxy is set as the back-end address. However, the error seen in F12 is still the local domain name, not the domain name after the proxy.)
2. To execute npm run dev again manually
4, Integration of shiro in front and back end separation project
Refer to:
blog.csdn.net/u013615903/article/details/78781166
Here are the problems encountered in the actual development integration process:
1. OPTIONS request without 'Authorization' request header field:
In front end and back end separation projects, because of cross domain, complex requests will be generated, that is, preflight requests will be sent, which will cause an OPTIONS request to be sent before GET / POST and other requests, but the OPTIONS request does not take shiro's' Authorization 'field (shiro's Session), that is, the OPTIONS request cannot pass shiro verification, and will return the unauthenticated information.
Solution: add a filter to shiro to filter OPTIONS requests
public class CORSAuthenticationFilter extends FormAuthenticationFilter { private static final Logger logger = LoggerFactory.getLogger(CORSAuthenticationFilter.class); public CORSAuthenticationFilter() { super(); } @Override public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //Always return true if the request's method is OPTIONSif (request instanceof HttpServletRequest) { if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) { return true; } } return super.isAccessAllowed(request, response, mappedValue); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletResponse res = (HttpServletResponse)response; res.setHeader("Access-Control-Allow-Origin", "*"); res.setStatus(HttpServletResponse.SC_OK); res.setCharacterEncoding("UTF-8"); PrintWriter writer = res.getWriter(); Map<String, Object> map= new HashMap<>(); map.put("code", 702); map.put("msg", "Not logged in"); writer.write(JSON.toJSONString(map)); writer.close(); return false; } }
Paste my config file:
@Configuration public class ShiroConfig { @Bean public Realm realm() { return new DDRealm(); } @Bean public CacheManager cacheManager() { return new MemoryConstrainedCacheManager(); } /** * cookie Object; * rememberMeCookie()The method is to set the cookie generation template, such as Cookie name, cookie validity time, etc. * @return */ @Bean public SimpleCookie rememberMeCookie(){ //System.out.println("ShiroConfiguration.rememberMeCookie()"); //This parameter is the name of the cookie, corresponding to the name of the front-end checkbox = rememberme SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //<! -- remember that my cookie takes effect in 30 days, in seconds; -- > simpleCookie.setMaxAge(259200); return simpleCookie; } /** * cookie Management object; * rememberMeManager()The method is to generate the rememberMe manager, and set the rememberMe manager to the security manager * @return */ @Bean public CookieRememberMeManager rememberMeManager(){ //System.out.println("ShiroConfiguration.rememberMeManager()"); CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); //The key encrypted by rememberMe cookie is recommended to be different for each item. The default AES algorithm key length is 128 256 512 bits cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag==")); return cookieRememberMeManager; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager sm = new DefaultWebSecurityManager(); sm.setRealm(realm()); sm.setCacheManager(cacheManager()); //Inject remember me manager sm.setRememberMeManager(rememberMeManager()); //Inject custom session manager sm.setSessionManager(sessionManager()); return sm; } //Custom session manager @Bean public SessionManager sessionManager() { return new CustomSessionManager(); } public CORSAuthenticationFilter corsAuthenticationFilter(){ return new CORSAuthenticationFilter(); } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); //SecurityUtils.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //Configure links that will not be blocked, and judge in order filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/static/js/**", "anon"); filterChainDefinitionMap.put("/static/css/**", "anon"); filterChainDefinitionMap.put("/static/fonts/**", "anon"); filterChainDefinitionMap.put("/login/**", "anon"); filterChainDefinitionMap.put("/corp/call_back/receive", "anon"); //authc:All URLs must be authenticated for access. anon: all URLs can be accessed anonymously filterChainDefinitionMap.put("/**", "corsAuthenticationFilter"); shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap); //Custom filter Map<String, Filter> filterMap = new LinkedHashMap<>(); filterMap.put("corsAuthenticationFilter", corsAuthenticationFilter()); shiroFilter.setFilters(filterMap); return shiroFilter; } /** * Shiro Life cycle processor * @ return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * Turn on Shiro's comments (e.g@ RequiresRoles,@RequiresPermissions )The following two beans (defaultadvisorautoproxycreator (optional) and AuthorizationAttributeSourceAdvisor) need to be configured to implement this function * @ return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
2. Set session expiration time
shiro session The default expiration time is 30 min,We're customizing sessionManager Set the expiration time to another value in the constructor of public class CustomSessionManager extends DefaultWebSessionManager { private static final Logger logger = LoggerFactory.getLogger(CustomSessionManager.class); private static final String AUTHORIZATION = "Authorization"; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; public CustomSessionManager() { super(); setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);//If Authorization exists in the request header, its value is sessionId if (!StringUtils.isEmpty(sessionId)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return sessionId; } else { //Otherwise, the sessionId is taken from the cookie according to the default rule return super.getSessionId(request, response); } } }
5, Deploy project
There are two main methods for front-end project deployment:
1. Package the front-end project (npm run build) into a static resource file, put it into the back-end, and package it together. The back-end writes a Controller to return to the front-end interface (I used Vue to develop a single page application), but in fact, the front and back ends are coupled together. However, at least the front and back ends are separated for development, so that the purpose of development is achieved and the requirements are preliminarily met. For the needs of the project, I do this and avoid cross domain problems.
@RequestMapping(value = {"/", "/index"}, method = RequestMethod.GET) public String index() { return "/index"; }
- Start another service (tomcat, nginx, nodejs) for the front-end project, which has cross domain problems.
Tell me about my problems:
1. nginx reverse proxy causes shiro 302 to unauth's controller when accessing an unauthorized page. The access address is https and the redirection address is http, which makes it impossible to access.
Without shiro shiroFilter.setLoginUrl("/unauth");
When the page has no permission to access, we directly return error information in the filter instead of using shiro's own jump. Look at the onAccessDenied function in the filter
public class CORSAuthenticationFilter extends FormAuthenticationFilter { private static final Logger logger = LoggerFactory.getLogger(CORSAuthenticationFilter.class); public CORSAuthenticationFilter() { super(); } @Override public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //Always return true if the request's method is OPTIONS if (request instanceof HttpServletRequest) { if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) { return true; } } return super.isAccessAllowed(request, response, mappedValue); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletResponse res = (HttpServletResponse)response; res.setHeader("Access-Control-Allow-Origin", "*"); res.setStatus(HttpServletResponse.SC_OK); res.setCharacterEncoding("UTF-8"); PrintWriter writer = res.getWriter(); Map<String, Object> map= new HashMap<>(); map.put("code", 702); map.put("msg", "Not logged in"); writer.write(JSON.toJSONString(map)); writer.close(); return false; } }
So many records first, there is something wrong, welcome to point out!
[yunqi online class] product technology experts share every day!
Course address: https://yqh.aliyun.com/zhibo
Join the community immediately, face to face with experts, and keep abreast of the latest news of the course!
[yunqi online classroom community] https://c.tb.cn/F3.Z8gvnK
Original release time: June 17, 2020
Author:_ Yufan
This article comes from:“ Internet architect WeChat official account ”, you can pay attention to“ Internet architect"