This is the 27th day of my participation in the August Genwen Challenge.More challenges in August
Exit data return
Jwt-username token – random code – redis JwtLogoutSuccessHandler
// Add Spring annotations
@Component
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {
// Inject JWT utility classes
@Autowired
JwtUtils jwtUtils;
@Override
public void onLogoutSuccess(HttpServletRequest Request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// Exit if it is null
if(authentication ! =null) {
new SecurityContextLogoutHandler().logout(Request,response,authentication);
}
// Format of the response
response.setContentType("application/json; charset=UTF-8");
/ / the output stream
ServletOutputStream outputStream = response.getOutputStream();
/ / headers
response.setHeader(jwtUtils.getHeader(), "");
// The result returned
Results result = Results.succ("");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8")); outputStream.flush(); outputStream.close(); }}Copy the code
No permission data is returned
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
// Set the header encoding specification
httpServletResponse.setContentType("application/json; charset=UTF-8");
// Give an underprivileged state
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
// Set the output stream
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
// Set error exception output
Results fail = Results.fail(e.getMessage());
// Write the format
outputStream.write(JSONUtil.toJsonStr(fail).getBytes("UTF-8"));
// Refresh to close the streamoutputStream.flush(); outputStream.close(); }}Copy the code
SpringSecurity is perfectly integrated into our project.
Resolve cross-domain problems
We use Postman for the above debugging. If we connect with the front end, there will be a cross-domain problem of CorsConfig
@Configuration
public class CorsConfig implements WebMvcConfigurer {
private CorsConfiguration buildConfig(a) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addExposedHeader("Authorization");
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter(a) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/ * *", buildConfig());
return new CorsFilter(source);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/ * *")
.allowedOrigins("*")
// .allowCredentials(true)
.allowedMethods("GET"."POST"."DELETE"."PUT")
.maxAge(3600); }}Copy the code
Menu interface development
Development menu interface, because these three tables: user table, role table, menu table, menu table, menu table is not need to obtain information through other tables. For example, users need to associate roles, and roles need to associate menus, while menus do not need to actively associate other tables. The link to get menu navigation and permissions is /sys/menu/nav, and the JSON data for our menu navigation should look like this:
{ title:
'Role Management',
icon: 'el-icon-rank',
path: '/sys/roles',
name: 'SysRoles',
component: 'sys/Role',
children: []}
Copy the code
The returned permission data should then be an array:
["sys:menu:list"."sys:menu:save"."sys:user:list". ]Copy the code
Notice that in the navigation menu there is a children, a submenu, which is a tree, because our menu might look like this:
System Management - Menu Management - Add menusCopy the code
This is a level 3 menu. Notice how this relationship relates. Our SysMenu entity class has parentId, but no children, so we can add children to SysMenu, but we can not add children, because we also need a DTO, so that we can return the json data format above. Add a children: SysMenu
@Data
@EqualsAndHashCode(callSuper = true)
public class SysMenu extends BaseEntity {
private static final long serialVersionUID = 1L;
/** * parent menu ID, level 1 menu is 0 */
@notnull (message = "Parent menu cannot be empty ")
private Long parentId;
@notblank (message = "Menu name cannot be empty ")
private String name;
/** * menu URL */
private String path;
/** * Authorization (multiple users are separated by commas, such as user:list,user:create) */
@notblank (message = "Menu authorization code cannot be blank ")
private String perms;
private String component;
/**
* 类型 0:目录 1:菜单 2:按钮
*/
@notnull (message = "Menu type cannot be empty ")
private Integer type;
/** * menu icon */
private String icon;
/** ** sort */
@TableField("orderNum")
private Integer orderNum;
@TableField(exist = false)
private List<SysMenu> children = new ArrayList<>();
}
Copy the code
SysMenuDto, knowing what data to return, we just need to fill in the data, right
package com.example.demo.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class SysMenuDto implements Serializable {
private Long id;
private String name;
private String title;
private String icon;
private String path;
private String component;
private List<SysMenuDto> children = new ArrayList<>();
}
Copy the code
SysMenuController
package com.example.demo.controller;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.Result.Results;
import com.example.demo.dto.SysMenuDto;
import com.example.demo.entity.SysMenu;
import com.example.demo.entity.SysRoleMenu;
import com.example.demo.entity.SysUser;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.List;
/** ** <p> * Front-end controller * </p> **@author fjj
* @sinceThe 2021-07-05 * /
@RestController
@RequestMapping("/sys/menu")
public class SysMenuController extends BaseController {
// Get the link to the menu
@GetMapping("/nav")
// Get nav navigation
public Results nav(Principal principal) {
// Get the current login user
SysUser byUsername = sysUserService.getByUsername(principal.getName());
// Access permission information is separated by commas
String userAuthorityInfo = sysUserService.getUserAuthorityInfo(byUsername.getId());
// Convert to an array
String[] strings = StringUtils.tokenizeToStringArray(userAuthorityInfo, ",");
// Get navigation information
List<SysMenuDto> navs = sysMenuService.getCurrentUserNav();
// Return the result
return Results.succ(MapUtil.builder()
.put("authoritys", strings)
.put("nav", navs)
.build()
);
}
// Get user information
@GetMapping("/userInfo")
public Results userInfo(Principal principal) {
SysUser sysUser = sysUserService.getByUsername(principal.getName());
return Results.succ(MapUtil.builder()
.put("id", sysUser.getId())
.put("username", sysUser.getUsername())
.put("avatar", sysUser.getAvatar())
.put("created", sysUser.getCreated())
.map()
);
}
@GetMapping("/info/{id}")
@PreAuthorize("hasAuthority('sys:menu:list')")
public Results info (@PathVariable(name = "id") Long id) {
return Results.succ(sysMenuService.getById(id));
}
@GetMapping("/list")
@PreAuthorize("hasAuthority('sys:menu:list')")
public Results list (a) {
List<SysMenu> menus = sysMenuService.tree ();
return Results.succ(menus);
}
/ / save
@PostMapping("/save")
@PreAuthorize("hasAuthority('sys:menu:save')")
public Results save (@Validated @RequestBody SysMenu sysMenu) {
sysMenu.setCreated(LocalDateTime.now());
sysMenuService.save(sysMenu);
return Results.succ(sysMenu);
}
/ / update
@PostMapping("/update")
@PreAuthorize("hasAuthority('sys:menu:update')")
public Results update (@Validated @RequestBody SysMenu sysMenu) {
sysMenu.setUpdated(LocalDateTime.now());
sysMenuService.updateById(sysMenu);
// Since this is an update operation, we need to be aware of the cache
sysUserService.clearUserAuthorityInfoByMenuId(sysMenu.getId());
return Results.succ(sysMenu);
}
/ / delete
@PostMapping("/delete/{id}")
@PreAuthorize("hasAuthority('sys:menu:delete')")
public Results delete (@PathVariable ("id") Long id) {
// Check whether the child node still exists and delete it if it does not
int parent_id = sysMenuService.count(new QueryWrapper<SysMenu>().eq("parent_id", id));
if (parent_id > 0) {
return Results.fail("Please delete the submenu first.");
}
// Clear all caches
sysUserService.clearUserAuthorityInfoByMenuId(id);
sysMenuService.removeById(id);
// Delete the associated table data
sysRoleMenuService.remove(new QueryWrapper<SysRoleMenu>().eq("menu_id",id));
return Results.succ("success"); }}Copy the code
The Principal Principal method is used to inject information about the current user. The getName method is used to obtain the current user name. SysUserService. GetUserAuthorityInfo methods we’ve said before, when we login to complete or authentication needs to return when user permissions. And then through the StringUtils. TokenizeToStringArray the string array form separated by a comma. Focus on and sysMenuService getcurrentUserNav, access to the current user menu navigation, SysMenuServiceImpl
package com.example.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.dto.SysMenuDto;
import com.example.demo.entity.SysMenu;
import com.example.demo.entity.SysUser;
import com.example.demo.mapper.SysMenuMapper;
import com.example.demo.mapper.SysUserMapper;
import com.example.demo.service.ISysMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/** * <p> * Service implementation class * </p> **@author fjj
* @sinceThe 2021-07-05 * /
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper.SysMenu> implements ISysMenuService {
@Autowired
SysUserServiceImpl sysUserService;
@Autowired
SysUserMapper sysUserMapper;
@Override
public List<SysMenuDto> getCurrentUserNav(a) {
// Get the login user
String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// Obtain the value by user name
SysUser sysUser = sysUserService.getByUsername(username);
// Get the menu Id by user Id
List<Long> navMenuIds = sysUserMapper.getNavMenuIds(sysUser.getId());
// Get the menu from the menu ID
List<SysMenu> sysMenus = this.listByIds(navMenuIds);
// Convert to a tree stump structure
List<SysMenu> menustree = buildTreeMeun(sysMenus);
// Entity class conversion
return convert(menustree);
}
@Override
public List<SysMenu> tree(a) {
// Get all menu information
List<SysMenu> sysMenus = this.list(new QueryWrapper<SysMenu>().orderByAsc("orderNum"));
// Convert to a tree stump structure
List<SysMenu> menus = buildTreeMeun(sysMenus);
return menus;
}
private List<SysMenuDto> convert(List<SysMenu> menustree) {
ArrayList<SysMenuDto> menuDtos = new ArrayList<>();
menustree.forEach(m -> {
SysMenuDto dto = new SysMenuDto();
dto.setId(m.getId());
dto.setName(m.getPerms());
dto.setTitle(m.getName());
dto.setComponent(m.getComponent());
dto.setPath(m.getPath());
// If the length is greater than that, the bytes are copied
if (m.getChildren().size() > 0) {
// The child node calls the current method to replicate.
dto.setChildren(convert(m.getChildren()));
}
menuDtos.add(dto);
});
return menuDtos;
}
// Convert the stump structure
private List<SysMenu> buildTreeMeun(List<SysMenu> sysMenus) {
// Prepare the returned list
ArrayList<SysMenu> finalMenus = new ArrayList<>();
// Find the respective child nodes
for (SysMenu sysMenu : sysMenus) {
for (SysMenu sysMenu1 : sysMenus) {
// If the id is the same, the child is your own
if(sysMenu.getId() == sysMenu1.getParentId()) { sysMenu.getChildren().add(sysMenu1); }}// Extract the parent node
if (sysMenu.getParentId() == 0L) { finalMenus.add(sysMenu); }}returnfinalMenus; }}Copy the code
Interface sysUserMapper. GetNavMenuIds we’ve written before, by user id for the id of the menu, and then is transformed into a tree structure, behind buildTreeMenu method of thinking is very simple, our reality the menu cycle, let all the child nodes of the menu to find their first, Then we get the top menu out, so there are two levels below the top, and two levels have their own three levels. Finally convert converts menu to menuDto. This one is easy, I won’t say it. Ok, navigation menu has been developed, let’s write the menu management add, delete, change and check, because the menu list is also a tree interface, this time we will not get the current user’s menu list, but all the menus and then form a tree structure, the same idea, different data. Delete, update the menu when remember to call the menu ID clear user permissions cache information method ha. Then each method is preceded by a permission annotation: @preauthorize (“hasAuthority(‘sys:menu:delete’)”), this requires users to have specific operation permissions to call this interface, sys:menu:delete These data are not written in random, we must be consistent with the database data. The Component field also communicates with the front end, because this is the component page linked to the front end. With add, delete, change and check, we go to add all our menu permission data first. The effect is as follows:
Role interface development
Add, delete, change and check the role is also simple, and so few fields SysRoleController
package com.example.demo.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.Result.Results;
import com.example.demo.entity.SysMenu;
import com.example.demo.entity.SysRole;
import com.example.demo.entity.SysRoleMenu;
import com.example.demo.entity.SysUserRole;
import com.example.demo.utils.Const;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/** ** <p> * Front-end controller * </p> **@author fjj
* @sinceThe 2021-07-05 * /
@RestController
@RequestMapping("/sys/role")
public class SysRoleController extends BaseController {
@PreAuthorize("hasAuthority('sys:role:list')")
@GetMapping("/info/{id}")
public Results info(@PathVariable("id") Long id) {
// Get the entity class
SysRole byId = sysRoleService.getById(id);
// Get the meunid of the associated table
List<SysRoleMenu> role_id = sysRoleMenuService.list(new QueryWrapper<SysRoleMenu>().eq("role_id", byId));
// Get the meunid from the stream
List<Long> collect = role_id.stream().map(p -> p.getMenuId()).collect(Collectors.toList());
byId.setMenuIds(collect);
return Results.succ(byId);
}
@PreAuthorize("hasAuthority('sys:role:list')")
@GetMapping("/list")
public Results list(String name) {
// check whether there is a name
Page page = sysRoleService.page(getPage(),
new QueryWrapper<SysRole>().like(StrUtil.isNotBlank(name), "name", name));
return Results.succ(page);
}
@PreAuthorize("hasAuthority('sys:role:save')")
@PostMapping("/save")
public Results save(@Validated @RequestBody SysRole sysRole) {
// Add a method
// Get the time of the current change
sysRole.setCreated(LocalDateTime.now());
// The saved state
sysRole.setStatu(Const.STATUS_ON);
sysRoleService.save(sysRole);
return Results.succ(sysRole);
}
@PreAuthorize("hasAuthority('sys:role:update')")
/ / update
@PostMapping("/update")
public Results update(@Validated @RequestBody SysRole sysRole) {
// Set the update time
sysRole.setCreated(LocalDateTime.now());
// Call the updated method
sysRoleService.updateById(sysRole);
// Delete the cache
sysUserService.clearUserAuthorityInfoByRoleId(sysRole.getId());
return Results.succ(sysRole);
}
// Batch delete
@PreAuthorize("hasAuthority('sys:role:delete')")
@PostMapping("/delete")
// Add transaction to avoid deletion failure
@Transactional
public Results delete(@RequestBody Long[] RoleIds) {
// Call the method
sysRoleService.removeByIds(Arrays.asList(RoleIds));
// Drop the intermediate table
sysRoleMenuService.remove(new QueryWrapper<SysRoleMenu>().in("role_id",RoleIds));
sysUserRoleService.remove(new QueryWrapper<SysUserRole>().in("role_id",RoleIds));
// Delete the cache
Arrays.stream(RoleIds).forEach(f -> {
sysUserService.clearUserAuthorityInfoByRoleId(f);
});
return Results.succ("success");
}
@PreAuthorize("hasAuthority('sys:role:perm')")
@PostMapping("/perm/{roleId}")
@Transactional
public Results info(@PathVariable("roleId") Long roleId,@RequestBody Long[] menuId) {
// The set to store
List<SysRoleMenu> sysRoleMenus = new ArrayList<>();
Arrays.stream(menuId).forEach(menuid -> {
SysRoleMenu roleMenu = new SysRoleMenu();
roleMenu.setMenuId(menuid);
roleMenu.setRoleId(roleId);
sysRoleMenus.add(roleMenu);
});
// Delete the record
sysRoleMenuService.remove(new QueryWrapper<SysRoleMenu>().eq("role_id",roleId));
// Save the new one
sysRoleMenuService.saveBatch(sysRoleMenus);
// Delete the cache
sysUserService.clearUserAuthorityInfoByRoleId(roleId);
returnResults.succ(menuId); }}Copy the code
In the above method: info method is used to obtain role information. This method is not only used when editing roles, but also used when displaying role association menus. Therefore, we need to query the IDS of all menus associated with roles, that is, the operation of assigning permissions. Corresponding to the front end is like this. Click Assign Permission, all menu lists will pop up, and then select the menu that has been associated according to the ID of the menu that has been associated with the role.
User interface development
User management there is a user associated role role assignment operation, and role associated menu writing similar
package com.example.demo.controller;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.Result.Results;
import com.example.demo.dto.PassDto;
import com.example.demo.entity.SysRole;
import com.example.demo.entity.SysRoleMenu;
import com.example.demo.entity.SysUser;
import com.example.demo.entity.SysUserRole;
import com.example.demo.utils.Const;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** ** <p> * Front-end controller * </p> **@author fjj
* @sinceThe 2021-07-05 * /
@RestController
@RequestMapping("/sys/user")
public class SysUserController extends BaseController {
// Inject encrypted
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping("/info/{id}")
@PreAuthorize("hasAuthority('sys:user:list')")
public Results info(@PathVariable("id") Long id) {
SysUser sysUser = sysUserService.getById(id);
// assert whether or not it is null
Assert.notNull(sysUser, "The administrator could not be found");
List<SysRole> sysRoles = sysRoleService.listRolesByUserId(id);
sysUser.setSysRoles(sysRoles);
return Results.succ(sysUser);
}
@PreAuthorize("hasAuthority('sys:user:list')")
@GetMapping("/list")
public Results list(String username) {
Page<SysUser> page = sysUserService.page(getPage(), new QueryWrapper<SysUser>().like(StringUtils.isNotBlank(username), "username", username));
page.getRecords().forEach(p -> {
p.setSysRoles(sysRoleService.listRolesByUserId(p.getId()));
});
return Results.succ(page);
}
@PreAuthorize("hasAuthority('sys:user:save')")
@PostMapping("/save")
public Results save(@Validated @RequestBody SysUser sysUser) {
// Set the update time
sysUser.setCreated(LocalDateTime.now());
// The default state
sysUser.setStatu(Const.STATUS_ON);
// Set the default encrypted password
String password = bCryptPasswordEncoder.encode(Const.PASS_WORD);
sysUser.setPassword(password);
// Set the default avatar
sysUser.setAvatar(Const.Avatar);
sysUserService.save(sysUser);
return Results.succ(sysUser);
}
@PreAuthorize("hasAuthority('sys:user:update')")
@PostMapping("/update")
public Results update(@Validated @RequestBody SysUser sysUser) {
// Set the update time
// sysUser.setCreated(LocalDateTime.now());
sysUser.setUpdated(LocalDateTime.now());
sysUserService.updateById(sysUser);
return Results.succ(sysUser);
}
@PreAuthorize("hasAuthority('sys:user:delete')")
@PostMapping("/delete")
@Transactional
public Results delete(@RequestBody Long [] ids) {
sysUserService.removeByIds(Arrays.asList(ids));
// Delete the middle relational table
sysUserRoleService.remove(new QueryWrapper<SysUserRole>().in("user_id",ids));
return Results.succ("");
}
@PreAuthorize("hasAuthority('sys:user:role')")
@PostMapping("/role/{userId}")
@Transactional
public Results rolePerm(@PathVariable Long userId,@RequestBody Long [] roleIds) {
ArrayList<SysUserRole> UserRoleList = new ArrayList<>();
Arrays.stream(roleIds).forEach(r ->{
SysUserRole userRole = new SysUserRole();
userRole.setRoleId(r);
userRole.setUserId(userId);
UserRoleList.add(userRole);
});
// Delete the associated table data
sysUserRoleService.remove(new QueryWrapper<SysUserRole>().eq("user_id",userId));
sysUserRoleService.saveBatch(UserRoleList);
// Delete the cache
SysUser sysUser = sysUserService.getById(userId);
sysUserService.clearUserAuthorityInfo(sysUser.getUsername());
return Results.succ("");
}
@PostMapping("/repass")
@PreAuthorize("hasAuthority('sys:user:repass')")
public Results repass(@RequestBody Long id) {
SysUser byId = sysUserService.getById(id);
byId.setPassword(bCryptPasswordEncoder.encode(Const.PASS_WORD));
byId.setCreated(LocalDateTime.now());
sysUserService.save(byId);
return Results.succ("");
}
// Personal center modification
@PostMapping("/updatePass")
public Results updatePass(@Validated @RequestBody PassDto passDto, Principal principal) {
SysUser sysUser = sysUserService.getByUsername(principal.getName());
boolean matches = bCryptPasswordEncoder.matches(passDto.getCurrentPass(), sysUser.getPassword());
if(! matches) {return Results.fail("Incorrect password");
}
sysUser.setPassword(bCryptPasswordEncoder.encode(Const.PASS_WORD));
sysUser.setUpdated(LocalDateTime.now());
sysUserService.updateById(sysUser);
return Results.succ(""); }}Copy the code
Top use a sysRoleService listRolesByUserId, through user id access to all of the associated role, with the middle table, can write SQL, so I write here
@Overridepublic List<SysRole> listRolesByUserId(Long userId) { return this.list( new QueryWrapper<SysRole>() .inSql("id"."select role_id from sys_user_role where user_id = "+ userId)); }Copy the code
UserId must be found in their own database, do not let the front-end pass anything directly call this method, otherwise it may be attacked
The end of the
All the resources in the resources can be downloaded.
Refer to the up main add link description