This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!
Zoe is an excellent backend framework.
Instead of separating the front and back ends, shiro+ Thymeleaf’s hierarchical framework is adopted in the management background. There are many modules and groups of projects, which are expected to meet the needs of distributed development. We can discuss the ideas in this paper together.
Upgrade ideas
- Write a generic Shiro authentication module into which all services are imported.
- The authentication center portal is set up. Users log in to the authentication center
- Session-based valid domain. All services use the same domain name. Shiro stores sessions through Redis.
See the previous article for ideas
Distributed Shiro permission verification 1
Distributed Shiro permission authentication II
Generic Shiro authentication module
Building module Ruoyi-dora-starter – Web, which other Web projects can introduce shiro authentication framework by introducing this starter.
Introduce Shiro dependencies
<! -- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
</dependency>
Copy the code
Write the SHro configuration
The main injection is the UserRealm credentialsMatcher SessionsSecurityManager
@Configuration
public class ShiroConfiguration {
/** * User authentication **/
@Bean
public UserRealm userRealm(a) {
UserRealm userRealm = new UserRealm();
// Set password authenticator
userRealm.setCredentialsMatcher(credentialsMatcher());
return userRealm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(a) {
// filterChain Filters static resources
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/static/**"."anon");
chainDefinition.addPathDefinition("/ajax/**"."anon");
chainDefinition.addPathDefinition("/css/**"."anon");
chainDefinition.addPathDefinition("/file/**"."anon");
chainDefinition.addPathDefinition("/fonts/**"."anon");
chainDefinition.addPathDefinition("/html/**"."anon");
chainDefinition.addPathDefinition("/i18n/**"."anon");
chainDefinition.addPathDefinition("/img/**"."anon");
chainDefinition.addPathDefinition("/js/**"."anon");
chainDefinition.addPathDefinition("/ruoyi/**"."anon");
chainDefinition.addPathDefinition("/login"."anon");
chainDefinition.addPathDefinition("/captcha"."anon");
chainDefinition.addPathDefinition("/logout"."anon");
chainDefinition.addPathDefinition("/ruoyi.png"."anon");
chainDefinition.addPathDefinition("/favicon.ico"."anon");
chainDefinition.addPathDefinition("/layuiadmin/**"."anon");
chainDefinition.addPathDefinition("/druid/**"."anon");
chainDefinition.addPathDefinition("/api/**"."anon");
chainDefinition.addPathDefinition("/ * *"."authc");
return chainDefinition;
}
@Bean
public HashedCredentialsMatcher credentialsMatcher(a) {
// Password authenticator
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// Md5Hash.ALGORITHM_NAME
credentialsMatcher.setHashAlgorithmName("SHA-256");
credentialsMatcher.setStoredCredentialsHexEncoded(false);
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
@Bean
public SessionsSecurityManager securityManager(a) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
Copy the code
Authentication and Authorization realm
To verify the feasibility, the authentication authorization is written temporarily
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) {
// Role permission information is temporarily written
User user = (User) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet();
Set<String> permissions = new HashSet();
if ("admin".equals(user.getUserName())) {
roles.add("admin");
permissions.add("op:write");
} else {
roles.add("user");
permissions.add("op:read");
}
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String)authenticationToken.getPrincipal();
String credentials = new String((char[])authenticationToken.getCredentials());
User user = new User();
user.setUserName(username);
String password = credentials;
// Password-authentication is the same as the injected Bean credentialsMatcher algorithm, which calculates hashedCredentials
// The actual need to fetch from the database
String salt = "salt";
int hashIterations = 1024;
String encodedPassword = (new SimpleHash("SHA-256", password, Util.bytes(salt), hashIterations)).toBase64();
log.info("password: {} encode: {}",password,encodedPassword);
user.setPassword(encodedPassword);
/ / authenticationToken getCredentials () + salt by compared with hashedCredentials credentialsMatcher encryption
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), Util.bytes(salt), this.getName());
return authenticationInfo;
}
Copy the code
Starter Automatic configuration
Add the configuration class “spring-Factories” to the meta-INF file
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.dora.web.config.DoraWebAutoConfiguration,\
com.ruoyi.dora.web.config.ShiroConfiguration
Copy the code
The starter authentication module is complete
Set up a certification center
Build the separate project Ruoyi-dora-portal-Web
The introduction ofruoyi-dora-starter-web
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-dora-starter-web</artifactId>
</dependency>
Copy the code
Import if the front – end resource file
Templates package and static resource file
The login control
@Slf4j
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage (Model model) {
if(SecurityUtils.getSubject().isAuthenticated()){
return "redirect:/index";
}
// Temporary processing depending on the configuration of the framework
Map<String, Object> configMap = new HashMap<>();
configMap.put("sys.account.registerUser".true);
model.addAttribute("config", configMap);
model.addAttribute("captchaEnabled".false);
return "login";
}
@GetMapping("/logout")
public String logout (a) {
SecurityUtils.getSubject().logout();
return "redirect:/login";
}
@PostMapping("/login")
@ResponseBody
public AjaxResult ajaxLogin (String username, String password, Boolean rememberMe) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return success();
} catch (AuthenticationException e) {
log.error("login error.",e);
String msg = "Incorrect user or password";
if (StringUtils.isNotEmpty(e.getMessage())) {
msg = e.getMessage();
}
returnerror(msg); }}@GetMapping("/unauth")
public String unauth (a) {
return "error/unauth"; }}Copy the code
Home page index
Main access to the database menu menu, and UI style Settings. ISysMenuService adopted mybatis – plus the generator generator code style, menuService. SelectMenusByUser see if, in accordance with the original code or gitee code in this paper. Some code to do temporary processing, write directly.
@Slf4j
@Controller
public class PortalController {
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysMenuService menuService;
@Autowired
private ISysConfigService configService;
@GetMapping({"/","/index"})
public String index(ModelMap modelMap){
Object principal = SecurityUtils.getSubject().getPrincipal();
log.info("principal {}",principal.toString());
SysUser user = sysUserService.getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getLoginName, "admin"), false);
List<SysMenu> menus = menuService.selectMenusByUser(user);
modelMap.put("menus", menus);
modelMap.put("user", user);
modelMap.put("sideTheme", configService.selectConfigByKey("sys.index.sideTheme").getConfigValue());
modelMap.put("skinName", configService.selectConfigByKey("sys.index.skinName").getConfigValue());
modelMap.put("ignoreFooter", configService.selectConfigByKey("sys.index.ignoreFooter").getConfigValue());
// Configure temporary processing
// modelMap.put("copyrightYear", RuoYiConfig.getCopyrightYear());
// modelMap.put("demoEnabled", RuoYiConfig.isDemoEnabled());
// modelMap.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
// modelMap.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
modelMap.put("copyrightYear"."2021");
modelMap.put("demoEnabled"."true");
modelMap.put("isDefaultModifyPwd".false);
modelMap.put("isPasswordExpired".false);
// Menu navigation display style
// String menuStyle = configService.selectConfigByKey("sys.index.menuStyle");
String menuStyle = "default";
// On mobile, the left navigation menu is set by default; otherwise, the default configuration is set
String indexStyle = menuStyle;
//ServletUtils.checkAgentIsMobile(ServletUtils.getRequest().getHeader("User-Agent")) ? "index" : menuStyle;
// Prioritize Cookie configuration navigation menu
// Cookie[] cookies = ServletUtils.getRequest().getCookies();
// for (Cookie cookie : cookies)
/ / {
// if (StringUtils.isNotEmpty(cookie.getName()) && "nav-style".equalsIgnoreCase(cookie.getName()))
/ / {
// indexStyle = cookie.getValue();
// break;
/ /}
/ /}
if("topnav".equalsIgnoreCase(indexStyle)){
return "index-topnav";
}
return "index";
}
/** * UI work area frame Main **/
@GetMapping("/system/main")
public String sysMain(Model model){
model.addAttribute("version"."0.0.1");
return "main"; }}Copy the code
At this point start the project, you can log in, and enter the index home page (menu item specific function has not been implemented, see if according to the original code).
Redis store session
This section describes how to manage sessions for multiple services in a unified manner.
Introduction of depend on
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
Copy the code
Yaml configuration redis
Add @enableredisHttpSession to the startup class and store the session in Redis
spring:
datasource:
url: jdbc:p6spy:mysql://localhost:3306/ry? useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
#redis
redis:
host: localhost
port: 6379
Copy the code
Chestnut sky – web
Set up a separate project, demo-sky-web
Introduce the above dependency ruoyi-dora-starter-web
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-dora-starter-web</artifactId>
</dependency>
Copy the code
Configure reIDS by pointing the login address of shrio to the address of ruoyi-dora-Portal-web authentication authority. Add the @enableredisHttpSession annotation to the startup class and store the session in Redis
shiro:
loginUrl: http://localhost:8600/login
spring:
redis:
host: localhost
port: 6379
Copy the code
Write a simple test page
@Controller public class SkyController {
@GetMapping("/sky") public String skyPage(a){ return "sky"; }}Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sky</title>
</head>
<body>
<h1>Sky sky</h1>
</body>
</html>
Copy the code
Database add menu, the menu will address address the full path to the demo – sky http://localhost:8080/sky, require domain name is the same (domain name for the localhost)
INSERT INTO `ry`.`sys_menu`(`menu_id`, `menu_name`, `parent_id`, `order_num`, `url`, `target`, `menu_type`, `visible`, `is_refresh`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (510.'Everyday Business Management'.5.1.'http://localhost:8080/sky'.' '.'C'.'0'.'1'.'system:sky:view'.'fa fa-user-o'.'admin'.'the 2021-07-01 02:17:28'.' '.NULL.'Daily Business Management Menu');
Copy the code
Direct access to jump to the login page, http://localhost:8600/login, http://localhost:8080/sky homepage to jump to the index after login, to access the menu every day activities can be normal access.
So far, the feasibility exploration of distributed project based on Shiro + Thymeleaf has been completed. In Ruoyi – Dora-starter -web, UserReaml user information, roles, and permissions can be obtained dynamically, such as through HTTP or Openfeign calling user services. If the functions of the original framework are not completely migrated, see the original framework as required.
conclusion
- Use the domain scope of the session, use the same domain name and Redis to centrally manage sessions and implement distributed session management.
- Abstract a public start for shirO configuration, the service module can be used to introduce start.
- The login page for all modules points to the unified login authentication authority.
If you are interested in this aspect, you can leave a message to button 1, and reply after the function is complete. Or see the Gitee code ruoyi-Dora for a study together.
Project Gitee Ruoyi – DORA