专栏:高并发---前后端分布式
👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者
📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶
📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
编写角色服务接口
接下来我们编写角色相关的CRUD方法,首先在通用模块编写角色服务接口:
public interface RoleService {
// 新增角色
void add(Role role);
// 修改角色
void update(Role role);
// 删除角色
void delete(Long id);
// 根据id查询角色
Role findById(Long id);
// 查询所有角色
List<Role> findAll();
// 分页查询角色
Page<Role> search(int page, int size);
// 修改角色的权限
void addPermissionToRole(Long rid, Long[] pids);
}
编写角色Mapper
1、在管理员服务模块编写角色Mapper
public interface RoleMapper extends BaseMapper<Role> {
// 根据id查询角色,包括权限
Role findById(Long id);
// 删除角色的所有权限
void deleteRoleAllPermission(Long rid);
// 删除用户_角色表的相关数据
void deleteRoleAllAdmin(Long rid);
// 给角色添加权限
void addPermissionToRole(@Param("rid") Long rid, @Param("pid")Long pid);
}
2、在 resources 中创建 RoleMapper 的同级包,编写映射文件 RoleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ittxc.shopping_admin_service.mapper.RoleMapper">
<resultMap id="roleMapper" type="com.itbaizhan.shopping_common.pojo.Role">
<id property="rid" column="rid"></id>
<result property="roleName" column="roleName"></result>
<result property="roleDesc" column="roleDesc"></result>
<collection property="permissions" column="rid" ofType="com.itbaizhan.shopping_common.pojo.Permission">
<id property="pid" column="pid"></id>
<result property="permissionName" column="permissionName"></result>
<result property="url" column="url"></result>
</collection>
</resultMap>
<select id="findById" parameterType="long" resultMap="roleMapper">
SELECT *
FROM bz_role
LEFT JOIN
bz_role_permission
ON bz_role.rid = bz_role_permission.rid
LEFT JOIN bz_permission
ON
bz_role_permission.pid = bz_permission.pid
WHERE bz_role.rid = #{rid}
</select>
<delete id="deleteRoleAllPermission" parameterType="long">
DELETE
FROM bz_role_permission
WHERE rid = #{rid}
</delete>
<delete id="deleteRoleAllAdmin" parameterType="long">
DELETE
FROM bz_admin_role
where rid = #{rid}
</delete>
<insert id="addPermissionToRole">
INSERT INTO bz_role_permission
VALUES (#{rid}, #{pid});
</insert>
</mapper>
编写角色服务实现类
在管理员服务模块编写角色服务实现类
@DubboService
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public void add(Role role) {
roleMapper.insert(role);
}
@Override
public void update(Role role) {
roleMapper.updateById(role);
}
@Override
public void delete(Long id) {
// 删除角色
roleMapper.deleteById(id);
// 删除角色的所有权限
roleMapper.deleteRoleAllPermission(id);
// 删除用户_角色中间表的相关数据
roleMapper.deleteRoleAllAdmin(id);
}
@Override
public Role findById(Long id) {
return roleMapper.findById(id);
}
@Override
public List<Role> findAll() {
return roleMapper.selectList(null);
}
@Override
public Page<Role> search(int page, int size) {
return roleMapper.selectPage(new Page(page,size),null);
}
@Override
public void addPermissionToRole(Long rid, Long[] pids) {
// 删除角色的所有权限
roleMapper.deleteRoleAllPermission(rid);
// 给角色添加权限
for (Long pid : pids) {
roleMapper.addPermissionToRole(rid,pid);
}
}
}
编写角色控制器
在后台管理Api模块编写角色控制器:
/**
* 后台角色
*/
@RestController
@RequestMapping("/role")
public class RoleController {
@DubboReference
private RoleService roleService;
/**
* 新增角色
*
* @param role 角色对象
* @return 执行结果
*/
@PostMapping("/add")
public BaseResult add(@RequestBody Role role) {
roleService.add(role);
return BaseResult.ok();
}
/**
* 修改角色
*
* @param role 角色对象
* @return 执行结果
*/
@PutMapping("/update")
public BaseResult update(@RequestBody Role role) {
roleService.update(role);
return BaseResult.ok();
}
/**
* 删除角色
*
* @param rid 角色id
* @return 执行结果
*/
@DeleteMapping("/delete")
public BaseResult delete(Long rid) {
roleService.delete(rid);
return BaseResult.ok();
}
/**
* 根据id查询角色
*
* @param rid
* @return 查询到的角色
*/
@GetMapping("/findById")
public BaseResult<Role> findById(Long rid) {
Role role = roleService.findById(rid);
return BaseResult.ok(role);
}
/**
* 分页查询角色
*
* @param page 页码
* @param size 每页条数
* @return 查询结果
*/
@GetMapping("/search")
public BaseResult<Page<Role>> search(int page, int size) {
Page<Role> page1 = roleService.search(page, size);
return BaseResult.ok(page1);
}
/**
* 查询所有角色
* @return 查询结果
*/
@GetMapping("/findAll")
public BaseResult<List<Role>> findAll() {
List<Role> all = roleService.findAll();
return BaseResult.ok(all);
}
/**
* 修改角色的权限
*
* @param rid 角色id
* @param pids 权限id
* @return 执行结果
*/
@PutMapping("/updatePermissionToRole")
public BaseResult updatePermissionToRole(Long rid, Long[] pids) {
roleService.addPermissionToRole(rid,pids);
return BaseResult.ok();
}
}
启动服务,测试角色控制器方法
编写权限服务接口
接下来我们编写权限相关的CRUD方法,首先在通用模块编写权限服务接口:
public interface PermissionService {
// 新增权限
void add(Permission permission);
// 修改权限
void update(Permission permission);
// 删除权限
void delete(Long id);
// 根据id查询权限
Permission findById(Long id);
// 分页查询权限
Page<Permission> search(int page, int size);
// 查询所有权限
List<Permission> findAll();
}
编写权限Mapper
1、在管理员服务模块编写权限Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
// 删除角色_权限表中的相关数据
void deletePermissionAllRole(Long pid)
}
2、编写权限Mapper映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ittxc.shopping_admin_service.mapper.PermissionMapper">
<delete id="deletePermissionAllRole" parameterType="long">
DELETE
FROM bz_role_permission
WHERE pid = #{pid}
</delete>
</mapper>
编写权限服务实现类
在管理员服务模块编写权限服务实现类
@DubboService
public class PermissionServiceImpl implements PermissionService {
@Autowired
private PermissionMapper permissionMapper;
@Override
public void add(Permission permission) {
permissionMapper.insert(permission);
}
@Override
public void update(Permission permission) {
permissionMapper.updateById(permission);
}
@Override
public void delete(Long id) {
// 删除权限
permissionMapper.deleteById(id);
// 删除角色_权限表中的相关数据
permissionMapper.deletePermissionAllRole(id);
}
@Override
public Permission findById(Long id) {
return permissionMapper.selectById(id);
}
@Override
public Page<Permission> search(int page, int size) {
return permissionMapper.selectPage(new Page(page,size),null);
}
@Override
public List<Permission> findAll() {
return permissionMapper.selectList(null);
}
}
编写权限控制器
在后台管理Api模块编写权限控制器:
/**
* 后台权限
*/
@RestController
@RequestMapping("/permission")
public class PermissionController {
@DubboReference
private PermissionService permissionService;
/**
* 新增权限
* @param permission 权限对象
* @return 执行结果
*/
@PostMapping("/add")
public BaseResult add(@RequestBody Permission permission){
permissionService.add(permission);
return BaseResult.ok();
}
/**
* 修改权限
* @param permission 权限对象
* @return 执行结果
*/
@PutMapping("/update")
public BaseResult update(@RequestBody Permission permission){
permissionService.update(permission);
return BaseResult.ok();
}
/**
* 删除权限
* @param pid 权限id
* @return 执行结果
*/
@DeleteMapping("/delete")
public BaseResult delete(Long pid){
permissionService.delete(pid);
return BaseResult.ok();
}
/**
* 根据id查询权限
* @param pid 权限id
* @return 查询结果
*/
@GetMapping("/findById")
public BaseResult<Permission> findById(Long pid){
Permission permission = permissionService.findById(pid);
return BaseResult.ok(permission);
}
/**
* 分页查询权限
* @param page 页面
* @param size 每页条数
* @return 查询结果
*/
@GetMapping("/search")
public BaseResult<Page<Permission>> search(int page,int size){
Page<Permission> permissionPage = permissionService.search(page, size);
return BaseResult.ok(permissionPage);
}
/**
* 查询所有权限
* @return 所有权限
*/
@GetMapping("/findAll")
public BaseResult<List<Permission>> findAll(){
List<Permission> all = permissionService.findAll();
return BaseResult.ok(all);
}
}
启动服务,测试权限控制器方法
编写Security处理器
接下来我们使用Spring Security编写管理员认证和授权功能。 Spring Security在访问接口时进行认证和授权,所以Spring Security的相关代码编写在管理员API模块。 之前使用Spring Security时,登录后会配置跳转页面。但百战商城 是前后端分离项目,所有认证和授权的结果,只是返回json字符串 让前端去处理。所以我们要创建 认证成功处理器 、 认证失败处理器 、 未登录处理 器 、 权限不足处理器 、 登出成功处理器 处理不同的结果,Spring Security通过 实现接口编写结果处理器。
1、在管理员API模块引入Spring Security的依赖
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、编写认证成功、认证失败处理器
// 登录成功处理器
public class MyLoginSuccessHandler
implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
BaseResult result = new BaseResult(200, "登录成功", null);
response.getWriter().write(JSON.toJSONString(result));
}
}
// 登录失败处理器
public class MyLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
BaseResult result = new BaseResult(402, "用户名或密码错误", null);
response.getWriter().write(JSON.toJSONString(result));
}
}
3、编写未登录处理器
// 未登录处理器
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
BaseResult result = new BaseResult(401, "用户名未登录", null);
response.getWriter().write(JSON.toJSONString(result));
}
}
4、编写权限不足处理器
// 权限不足处理器
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
BaseResult result = new BaseResult(403, "权限不足", null);
response.getWriter().write(JSON.toJSONString(result));
}
}
5、编写登出成功处理器
// 登出成功处理器
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {
response.setContentType("text/json;charset=utf-8");
BaseResult result = new BaseResult(200, "注销成功", null);
response.getWriter().write(JSON.toJSONString(result));
}
}
编写Security配置类
在后台管理API模块编写Spring Security配置类
// Security配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Spring Security配置
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义表单登录
http.formLogin()
.usernameParameter("username") // 用户名项
.passwordParameter("password") // 密码项
.loginProcessingUrl("/admin/login") // 登录提交路径
.successHandler(new MyLoginSuccessHandler()) // 登录成功处理器
.failureHandler(new MyLoginFailureHandler()); // 登录失败处理器
// 权限拦截配置
http.authorizeRequests()
.antMatchers("/login").permitAll() // 登录页不需要认证
.antMatchers("/admin/login").permitAll() //登录请求不需要认证
.anyRequest().authenticated(); // 其余请求都需要认证
// 退出登录配置
http.logout()
.logoutUrl("/admin/logout")// 注销的路径
.logoutSuccessHandler(new MyLogoutSuccessHandler()) // 登出成功处理器
.clearAuthentication(true)// 清除认证数据
.invalidateHttpSession(true); // 清除session
// 异常处理
http.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未登录处理器
.accessDeniedHandler(new MyAccessDeniedHandler()); // 权限不足处理器
// 关闭csrf防护
http.csrf().disable();
// 开启跨域访问
http.cors();
}
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
编写认证授权相关的服务方法
1、在管理员服务接口添加 根据用户名查询管理员 和 根据用户名查询权限 方法
// 根据名字查询管理员
Admin findByAdminName(String username);
// 根据名字查询管理员所有权限
List<Permission> findAllPermission(String username);
2、在管理员服务模块编写管理员Mapper
// 根据管理员名查询权限
List<Permission> findAllPermission(String username);
编写AdminMapper.xml
<select id="findAllPermission" resultType="com.itbaizhan.shopping_pojo.pojo.Permission" parameterType="string">
SELECT
DISTINCT bz_permission.*
FROM
bz_admin
LEFT JOIN bz_admin_role
ON bz_admin.aid =
bz_admin_role.aid
LEFT JOIN bz_role
ON bz_admin_role.rid = bz_role.rid
LEFT JOIN bz_role_permission
ON bz_role.rid = bz_role_permission.rid
LEFT JOIN bz_permission
ON
bz_role_permission.pid = bz_permission.pid
WHERE bz_admin.username = #{username}
</select>
3、在管理员服务模块编写管理员服务接口实现类
@Override
public Admin findByAdminName(String username) {
QueryWrapper<Admin> wrapper = new QueryWrapper();
wrapper.eq("username", username);
Admin admin = adminMapper.selectOne(wrapper);
return admin;
}
@Override
public List<Permission> findAllPermission(String username) {
return adminMapper.findAllPermission(username);
}
编写认证授权逻辑
在后台管理API模块编写认证和授权逻辑
@Service
public class MyUserDetailService implements
UserDetailsService {
@DubboReference
private AdminService adminService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.认证
Admin admin = adminService.findByAdminName(username);
if(admin == null){
throw new UsernameNotFoundException("用户不存在");
}
// 2.授权
List<Permission> permissions = adminService.findAllPermission(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (Permission permission : permissions) {
grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl()));
}
// 3.封装为UserDetails对象
UserDetails userDetails = User.withUsername(admin.getUsername())
.password(admin.getPassword())
.authorities(grantedAuthorities)
.build();
// 4.返回封装好的UserDetails对象
return userDetails;
}
}
编写接口鉴权配置
我们要对接口进行鉴权配置,即用户拥有权限才能访问接口。
1、开启鉴权配置注解
// Security配置类
@Configuration
// 开启鉴权配置注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
}
2、在需要鉴权的接口上方添加鉴权注解
/**
* 分页查询管理员
* @param page 页码
* @param size 每天条数
* @return 查询结果
*/
@GetMapping("/search")
@PreAuthorize("hasAnyAuthority('/admin/all')")
public BaseResult<Page<Admin>> search(int page, int size){
Page<Admin> adminPage = adminService.search(page, size);
return BaseResult.ok(adminPage);
}
/**
* 分页查询角色
* @param page 页码
* @param size 每页条数
* @return 查询结果
*/
@GetMapping("/search")
@PreAuthorize("hasAnyAuthority('/role/all')")
public BaseResult<Page<Role>> search(int page, int size){
Page<Role> rolePage = roleService.search(page, size);
return BaseResult.ok(rolePage);
}
3、使用不同权限的用户登录,查看他们是否能访问这些接口
测试时,当用户权限不足时,系统会抛出500异常,这是由于全 局异常处理器先处理了异常,使得异常没有交由 AccessDeniedHandler 。此时我们需要在管理员API模块添加异常处理 器,当捕获到 AccessDeniedException 异常时,直接抛出,此时异常就 会交给 AccessDeniedHandler 处理。
// 统一异常处理器
@RestControllerAdvice
public class AccessDeniedExceptionHandler
{
// 处理权限不足异常,捕获到异常后再次抛出,交给 AccessDeniedHandler处理
@ExceptionHandler(AccessDeniedException.class)
public void defaultExceptionHandler(AccessDeniedException e) throws AccessDeniedException{
throw e;
}
}
修改新增/修改管理员方法
接下来修改新增用户和修改用户方法,对密码进行加密:
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
private PasswordEncoder encoder;
/**
* 新增管理员
* @param admin 管理员对象
* @return 执行结果
*/
@PostMapping("/add")
public BaseResult add(@RequestBody Admin admin) {
String password = admin.getPassword();
password = encoder.encode(password);
admin.setPassword(password);
adminService.add(admin);
return BaseResult.ok();
}
/**
* 修改管理员
* @param admin 管理员对象
* @return 执行结果
*/
@PutMapping("/update")
public BaseResult update(@RequestBody Admin admin) {
String password = admin.getPassword();
if (StringUtils.hasText(password)){
// 密码不为空加密
password = encoder.encode(password);
admin.setPassword(password);
}
adminService.update(admin);
return BaseResult.ok();
}
}
@DubboService
public class AdminServiceImpl implements
AdminService {
@Override
public void update(Admin admin) {
// 如果前端传来空密码,则密码还是原来的密码
if(!StringUtils.hasText(admin.getPassword())){
// 查询原来的密码
String password = adminMapper.selectById(admin.getAid()).getPassword();
admin.setPassword(password);
}
adminMapper.updateById(admin);
}
}
编写获取登录管理员名方法
/**
* 获取登录管理员名
*
* @return 管理员名
*/
@GetMapping("/getUsername")
public BaseResult<String> getUsername() {
// 1.获取会话对象
SecurityContext context = SecurityContextHolder.getContext();
// 2.获取认证对象
Authentication authentication = context.getAuthentication();
// 3.获取登录用户信息
UserDetails userDetails = (UserDetails)authentication.getPrincipal();
String username = userDetails.getUsername();
return BaseResult.ok(username);
}