Java --- 云尚办公之权限管理模块

news2025/1/11 8:18:58

目录

一、权限管理

二、JWT

三、用户登录功能实现

四、用户登录后的信息

五、前端代码

六、spring-security

6.1、用户认证 

6.2、用户授权


一、权限管理

粗粒度权限:

不同用户进入系统,因权限不同看到菜单不同

细粒度权限:

在一个页面内不同权限的人对某个功能的操作

 总体实现:

用户进行登录,登录后根据用户的权限大小进行相关系统操作

用户登录:

1、根据用户输入信息查询数据库,判断信息是否正确

2、用户信息存在,判断用户是否被禁用

3、登录成功后保存登录状态(使用token实现)

🎈token实现:

😒、使用登录信息生成唯一的字符串,对生成字符串进行编码加密处理。

😒、把唯一的字符串放到cookie里面,从cookie获取用户信息。

😒、cookie跨越问题:不能进行跨域,前后端端口号不一样。

😒、cookie跨越解决办法:每次发送请求时候,把cookie值获取处理放到请求头,每次从请求头获取用户信息。

登录成功,获取用户操作权限:

😍、从请求头获取token字符串,从字符串获取用户id

😍、根据用户id查询、用户可以操作菜单和按钮

二、JWT

介绍:

🤣、JWT是JSON Web Token的缩写,即JSON Web令牌,是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。

🤣、JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。

🤣、JWT最重要的作用就是对 token信息的防伪作用。

JWT令牌组成:

❤️:JWT头、有效载荷、签名哈希。

集成JWT:

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
//jwt工具类
public class JwtHelper {
    private static long tokenExpiration = 365 * 24 * 60 * 60 * 1000;
    private static String tokenSignKey = "123456";
    //根用户id和用户名称生成token字符串
    public static String createToken(Long userId, String username) {
        String token = Jwts.builder()
                //分类
                .setSubject("AUTH-USER")
                //设置token有效时长
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                //设置主体
                .claim("userId", userId)
                .claim("username", username)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                //签名部分
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }
    //从生成的token中获取用户ID
    public static Long getUserId(String token) {
        try {
            if (StringUtils.isEmpty(token)) return null;

            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            Integer userId = (Integer) claims.get("userId");
            return userId.longValue();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    //从生成token字符串获取用户名称
    public static String getUsername(String token) {
        try {
            if (StringUtils.isEmpty(token)) return "";

            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return (String) claims.get("username");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        String root = JwtHelper.createToken(1L, "root");
        System.out.println(root);
        Long userId = JwtHelper.getUserId(root);
        String username = JwtHelper.getUsername(root);
        System.out.println("userId:" + userId + "username:" + username);
    }
}

三、用户登录功能实现

MD5加密密码:

public final class MD5 {

    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!+" + e);
        }
    }

    public static void main(String[] args) {
        System.out.println(MD5.encrypt("111111"));
    }
}

 修改添加用户时密码使用加密方式:

@ApiOperation("添加用户")
    @PostMapping("/addUser")
    public Result addUser(@RequestBody SysUser sysUser){
        //使用MD5进行密码加密
        String password = sysUser.getPassword();
        String encrypt = MD5.encrypt(password);
        sysUser.setPassword(encrypt);
        boolean save = sysUserService.save(sysUser);
        if (save){
            return Result.ok();
        }
        return Result.fail();
    }

用户登录功能:

    //用户登录
    @Override
    public Map<String ,Object> login(LoginVo loginVo) {
        //根据用户名查询数据库
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getUsername,loginVo.getUsername());
        SysUser sysUser = baseMapper.selectOne(wrapper);
        //判断用户是否存在
        if (sysUser == null){
            throw new CJCException(201,"用户不存在");
        }
        //判断密码是否正确
        String encrypt = MD5.encrypt(loginVo.getPassword());
        if (!encrypt.equals(sysUser.getPassword())){
            throw new CJCException(201,"密码不正确");
        }
        //判断用户是否禁用
        if (sysUser.getStatus().intValue() == 0){
            throw new CJCException(201,"用户已禁用");
        }
        //使用jwt生成token
        String token = JwtHelper.createToken(sysUser.getId(), sysUser.getUsername());
        HashMap<String , Object> map = new HashMap<>();
        map.put("token",token);
        return map;
    }
  @ApiOperation("用户登录")
    @PostMapping("/login")
    public Result login(@RequestBody LoginVo loginVo){
        return Result.ok(sysUserService.login(loginVo));
    }

四、用户登录后的信息

 @Override
    public Map<String, Object> info(HttpServletRequest request) {
        //从请求头获取用户信息
        String token = request.getHeader("token");
        //使用token字符串获取用户id或用户名
        Long userId = JwtHelper.getUserId(token);
        //使用用户Id查询用户信息
        SysUser sysUser = baseMapper.selectById(userId);
        //根据用户id获取用户可以操作的菜单列表
       List<RouterVo> routerVoList = sysMenuService.findUserMenuListByUserId(userId);
       //根据用户id获取用户按钮列表
        List<String> list = sysMenuService.findUserPermsByUserId(userId);
        //封装数据并返回
        HashMap<String , Object> map = new HashMap<>();
        map.put("roles",sysUser.getUsername());
        map.put("name",sysUser.getName());
        map.put("routers",routerVoList);
        map.put("buttons",list);
        return map;
    }
//根据用户id获取用户可以操作的菜单列表
    @Override
    public List<RouterVo> findUserMenuListByUserId(Long userId) {
        List<SysMenu> sysMenuList = null;
         //判断是否是管理员,是就查询所有菜单
         if (userId.longValue() == 1){
             LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
             wrapper.eq(SysMenu::getStatus,1)
                     .orderByAsc(SysMenu::getSortValue);
             sysMenuList = baseMapper.selectList(wrapper);
         }else {
             //不是就根据用户id查询操作菜单列表
             sysMenuList =  baseMapper.findMenuListByUserId(userId);
         }
         //构建树形结构
        List<SysMenu> sysMenuTreeList = MenuHelper.buildTree(sysMenuList);
        List<RouterVo> routerVoList =  this.buildRouter(sysMenuTreeList);
        return routerVoList;
    }
    //构建需要的树形结构
    private List<RouterVo> buildRouter(List<SysMenu> sysMenuTreeList) {
        //创建集合,用于封装数据
        ArrayList<RouterVo> routerVos = new ArrayList<>();
        //遍历集合
        for (SysMenu sysMenu:sysMenuTreeList) {
            RouterVo routerVo = new RouterVo();
            routerVo.setHidden(false);
            routerVo.setAlwaysShow(false);
            routerVo.setPath(getRouterPath(sysMenu));
            routerVo.setComponent(sysMenu.getComponent());
            routerVo.setMeta(new MetaVo(sysMenu.getName(),sysMenu.getIcon()));
            //下一层数据
            List<SysMenu> children = sysMenu.getChildren();
            if (sysMenu.getType().intValue() == 1){
                //加载出隐藏路由
                List<SysMenu> collect = children.stream().filter(c -> StringUtils.hasLength(c.getComponent()))
                        .collect(Collectors.toList());
                for (SysMenu sysMenuType:collect) {
                    RouterVo hiddenMenu = new RouterVo();
                    hiddenMenu.setHidden(true);
                    hiddenMenu.setAlwaysShow(false);
                    hiddenMenu.setPath(getRouterPath(sysMenuType));
                    hiddenMenu.setComponent(sysMenuType.getComponent());
                    hiddenMenu.setMeta(new MetaVo(sysMenuType.getName(),sysMenuType.getIcon()));
                    routerVos.add(hiddenMenu);
                }
            }else {
                if (!CollectionUtils.isEmpty(children)){
                    if (children.size() > 0){
                        routerVo.setAlwaysShow(true);
                    }
                    //递归调用
                    routerVo.setChildren(buildRouter(children));
                }
            }
            routerVos.add(routerVo);
        }
        return routerVos;
    }
    //获取路由
    public String getRouterPath(SysMenu menu) {
        String routerPath = "/" + menu.getPath();
        if(menu.getParentId().intValue() != 0) {
            routerPath = menu.getPath();
        }
        return routerPath;
    }
    @Override
    public List<String> findUserPermsByUserId(Long userId) {
        List<SysMenu> sysMenuList = null;
        //判断是否是管理员
        if (userId.longValue() == 1){
            LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SysMenu::getStatus,1);
            sysMenuList = baseMapper.selectList(wrapper);
        }else {
            //不是管理员,根据id查询可以操作按钮的权限
            sysMenuList = baseMapper.findMenuListByUserId(userId);
        }
        //判断取值,存入list集合
        List<String> collect = sysMenuList.stream()
                .filter(c -> c.getType() == 2)
                .map(c -> c.getPerms())
                .collect(Collectors.toList());
        return collect;
    }

mapper层

@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
   //多表查询:不是管理员,根据id查询可以操作按钮的权限
    List<SysMenu> findMenuListByUserId(@Param("userId") Long userId);
}

mapper.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.cjc.auth.mapper.SysMenuMapper">
    <resultMap id="sysMenuMap" type="com.cjc.model.system.SysMenu" autoMapping="true">
    </resultMap>
 <select id="findMenuListByUserId" resultMap="sysMenuMap">
     SELECT DISTINCT
         m.id,m.parent_id,m.name,m.type,m.path,m.component,m.perms,m.icon,m.sort_value,m.status,m.create_time,m.update_time,m.is_deleted
     FROM sys_menu m
              INNER JOIN sys_role_menu rm ON rm.menu_id = m.id
              INNER JOIN sys_user_role ur ON ur.role_id = rm.role_id
     WHERE ur.user_id = #{userId}
       AND m.status = 1
       AND rm.is_deleted = 0
       AND ur.is_deleted = 0
       AND m.is_deleted = 0;
 </select>
</mapper>

controller层

  @ApiOperation("登录后请求信息")
    @GetMapping("/info")
    public Result info(HttpServletRequest request){
        Map<String, Object> map = sysUserService.info(request);
        return Result.ok(map);
    }

测试报错:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.cjc.auth.mapper.SysMenuMapper.findMenuListByUserId
    at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.<init>(MybatisMapperMethod.java:51)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.lambda$cachedInvoker$0(MybatisMapperProxy.java:111)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    at com.baomidou.mybatisplus.core.toolkit.CollectionUtils.computeIfAbsent(CollectionUtils.java:117)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.cachedInvoker(MybatisMapperProxy.java:98)

解决办法:

五、前端代码

request.js

  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['token'] = getToken()
    }
    return config
  },

 store/modules/user.js

const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    buttons: [], // 新增
    menus: '' //新增
  }
}
const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  // 新增
  SET_BUTTONS: (state, buttons) => {
    state.buttons = buttons
  },
  // 新增
  SET_MENUS: (state, menus) => {
    state.menus = menus
  }
}

 store/getters.js

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  //新增
  buttons: state => state.user.buttons,
  menus: state => state.user.menus
}

在router这个目录下新建两个js文件

_import_production.js
// 生产环境导入组件

module.exports = file => () => import('@/views/' + file + '.vue')

_import_development.js
// 开发环境导入组件

module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+

src/permission.js

import router from './router'
import store from './store'
import { getToken } from '@/utils/auth'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // 水平进度条提示: 在跳转路由时使用
import 'nprogress/nprogress.css' // 水平进度条样式
import getPageTitle from '@/utils/get-page-title' // 获取应用头部标题的函数
import Layout from '@/layout'
import ParentView from '@/components/ParentView'
const _import = require('./router/_import_'+process.env.NODE_ENV) // 获取组件的方法

NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
  NProgress.start()
// set page title
  document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo')// 请求获取用户信息
          if (store.getters.menus.length < 1) {
            global.antRouter = []
            next()
          }
          const menus = filterAsyncRouter(store.getters.menus)// 1.过滤路由
          console.log(menus)
          router.addRoutes(menus) // 2.动态添加路由
          let lastRou = [{ path: '*', redirect: '/404', hidden: true }]
          router.addRoutes(lastRou)
          global.antRouter = menus // 3.将路由数据传递给全局变量,做侧边栏菜单渲染工作
          next({
            ...to,
            replace: true
          })
          //next()
        } catch (error) {
          // remove token and go to login page to re-login
          console.log(error)
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else { /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => { // finish progress bar
  NProgress.done()
}) // // 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') {
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else {
        try {
          route.component = _import(route.component)// 导入组件
        } catch (error) {
          debugger
          console.log(error)
          route.component = _import('dashboard/index')// 导入组件
        }
      }
    }
    if (route.children && route.children.length > 0) {
      route.children = filterAsyncRouter(route.children)
    } else {
      delete route.children
    }
    return true
  })
  return accessedRouters
}

src/router下删除index.js中自定义的路由

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  // {
  //   path: '/404',
  //   component: () => import('@/views/404'),
  //   hidden: true
  // },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  }


  //添加我们的路由
  // {
  //   path: '/system',
  //   component: Layout,
  //   meta: {
  //     title: '系统管理',
  //     icon: 'el-icon-s-tools'
  //   },
  //   alwaysShow: true,
  //   children: [
  //     {
  //       path: 'sysRole',
  //       component: () => import('@/views/system/sysRole/list'),
  //       meta: {
  //         title: '角色管理',
  //         icon: 'el-icon-s-help'
  //       },
  //     },
  //     {
  //       path: 'sysUser',
  //       component: () => import('@/views/system/sysUser/list'),
  //       meta: {
  //         title: '用户管理',
  //         icon: 'el-icon-s-help'
  //       },
  //     },

  //     {
  //       name: 'sysMenu',
  //       path: 'sysMenu',
  //       component: () => import('@/views/system/sysMenu/list'),
  //       meta: {
  //         title: '菜单管理',
  //         icon: 'el-icon-s-unfold'
  //       },
  //     },

  //     {
  //       path: 'assignAuth',
  //       component: () => import('@/views/system/sysRole/assignAuth'),
  //       meta: {
  //         activeMenu: '/system/sysRole',
  //         title: '角色授权'
  //       },
  //       hidden: true,
  //     }
  //   ]
  // },


  // 404 page must be placed at the end !!!
  // { path: '*', redirect: '/404', hidden: true }
]

在scr/components目录下新建ParentView文件夹,添加index.vue

<template >
  <router-view />
</template>

layout/components/SideBar/index.vue

computed: {
  ...mapGetters([
    'sidebar'
  ]),
  routes() {
    //return this.$router.options.routes
    return this.$router.options.routes.concat(global.antRouter)
  },

utils/btn-permission.js

import store from '@/store'

/**
 * 判断当前用户是否有此按钮权限
 * 按钮权限字符串 permission 
 */
export default function hasBtnPermission(permission) {
  // 得到当前用户的所有按钮权限
  const myBtns = store.getters.buttons
  // 如果指定的功能权限在myBtns中, 返回true ==> 这个按钮就会显示, 否则隐藏
  return myBtns.indexOf(permission) !== -1
}

main.js

//新增
import hasBtnPermission from '@/utils/btn-permission'
Vue.prototype.$hasBP = hasBtnPermission

views/login/index.vue

const validateUsername = (rule, value, callback) => {
  if (value.length<4) {
    callback(new Error('Please enter the correct user name'))
  } else {
    callback()
  }
}
const validatePassword = (rule, value, callback) => {
  if (value.length < 6) {
    callback(new Error('The password can not be less than 6 digits'))
  } else {
    callback()
  }
}

按钮权限控制

<el-button type="success" icon="el-icon-plus" size="mini" @click="add" :disabled="$hasBP('bnt.sysRole.add')  === false">添 加</el-button>

六、spring-security

 导入pom依赖:

<dependencies>
        <dependency>
            <groupId>org.cjc</groupId>
            <artifactId>common-util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Spring Security依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>
    </dependencies>

config配置:

@AutoConfiguration
@EnableWebSecurity
public class WebSecurityConfig  {

}

测试:

6.1、用户认证 

用户认证流程:

 自定义加密器处理组件:

@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}

自定义UserDetails:

public class CustomUser extends User {
    /**
     * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
     */
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

    public SysUser getSysUser() {
        return sysUser;
    }

    public void setSysUser(SysUser sysUser) {
        this.sysUser = sysUser;
    }
}

 自定义loadUserByUsername:

public interface UserDetailsService {
    /**
     * 根据用户名获取用户对象(获取不到直接抛异常)
     */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
 //根据用户名查询
    @Override
    public SysUser findUserByUserName(String username) {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getUsername,username);
        SysUser sysUser = baseMapper.selectOne(wrapper);
        return sysUser;
    }
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserService sysUserService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询
       SysUser sysUser =  sysUserService.findUserByUserName(username);
       if (sysUser == null){
           throw  new UsernameNotFoundException("用户名不存在");
       }
       if (sysUser.getStatus() == 0){
           throw new RuntimeException("账号已失效");
       }
       return new CustomUser(sysUser, Collections.emptyList());
    }
}

核心组件:

😘、登录filter:判断用户名是否正确,生成token

public class ResponseUtil {
    public static void out(HttpServletResponse response, Result r) {
        ObjectMapper mapper = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            mapper.writeValue(response.getWriter(), r);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    //构造方法
    public TokenLoginFilter(AuthenticationManager manager){
       this.setAuthenticationManager(manager);
       this.setPostOnly(false);
       //指定登录接口及提交方式
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));
    }
    //登录认证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        try {
            //获取用户信息
            LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
            //封装对象
            Authentication usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
            //调用方法
            return this.getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }
    //成功认证方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
       //获取当前用户
        CustomUser customUser = (CustomUser) authResult.getPrincipal();
        //生成token
        String token = JwtHelper
                .createToken(customUser.getSysUser().getId(),
                customUser.getSysUser().getUsername());
        //返回
        HashMap<String , Object> map = new HashMap<>();
        map.put("token",token);
        ResponseUtil.out(response, Result.ok(map));
    }

    //认证失败调用方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtil.out(response,Result.build(null, ResultCodeEnum.LOGIN_ERROR));
    }
}

😊、认证解析token组件:判断请求头是否有token

public class TokenAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //如果是登录接口,直接放行
        if("/admin/system/index/login".equals(request.getRequestURI())) {
            filterChain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
        if(null != authentication) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
            filterChain.doFilter(request, response);
        } else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
        }
    }
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (StringUtils.hasLength(token)) {
            String useruame = JwtHelper.getUsername(token);
            logger.info("useruame:"+useruame);
            if (StringUtils.hasLength(useruame)) {
                return new UsernamePasswordAuthenticationToken(useruame, null, Collections.emptyList());
            }
        }
        return null;
    }
}

😁、在配置类里配置相关信息

@AutoConfiguration
@EnableWebSecurity
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private CustomMd5PasswordEncoder customMd5PasswordEncoder;

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager() ;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
        http
                //关闭csrf跨站请求伪造
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                //TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
                .addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new TokenLoginFilter(authenticationManager()));

        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(customMd5PasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
    }
}

报错:

Description:

Field userDetailsService in com.cjc.security.config.WebSecurityConfig required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' in your configuration.
解决办法:

public interface UserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService {
    /**
     * 根据用户名获取用户对象(获取不到直接抛异常)
     */
    @Override
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

6.2、用户授权

 实现步骤:

😎、进行登录,根据用户名查询用户操作权限,查询当前用户登录用户操作权限数据放到redis中。

😎、校验时,从请求头获取token字符串,从token获取username在到redis中查询权限数据

代码实现:

根据用户查询操作权限数据,封装返回。

 @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询
       SysUser sysUser =  sysUserService.findUserByUserName(username);
       if (sysUser == null){
           throw  new UsernameNotFoundException("用户名不存在");
       }
       if (sysUser.getStatus() == 0){
           throw new RuntimeException("账号已失效");
       }
       //查询用户操作权限
        List<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());
        //封装最终权限数据
       ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
        //遍历封装
        for (String userPerms:userPermsList) {
            authorities.add(new SimpleGrantedAuthority(userPerms.trim()));
        }
        return new CustomUser(sysUser, authorities);
    }

增加权限数据部分

     
    private  StringRedisTemplate stringRedisTemplate;
    //构造方法
    public TokenLoginFilter(AuthenticationManager manager,StringRedisTemplate stringRedisTemplate){
       this.setAuthenticationManager(manager);
       this.setPostOnly(false);
       //指定登录接口及提交方式
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));
        this.stringRedisTemplate = stringRedisTemplate;
    }

  @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
       //获取当前用户
        CustomUser customUser = (CustomUser) authResult.getPrincipal();
        //生成token
        String token = JwtHelper
                .createToken(customUser.getSysUser().getId(),
                customUser.getSysUser().getUsername());
        //获得当前用户权限数据,存入redis中,key:用户名,value:权限数据
        stringRedisTemplate.opsForValue().set(customUser.getUsername(), JSON.toJSONString(customUser.getAuthorities()));
        //返回
        HashMap<String , Object> map = new HashMap<>();
        map.put("token",token);
        ResponseUtil.out(response, Result.ok(map));
    }

从请求头获取token,根据username查询redis

private StringRedisTemplate stringRedisTemplate;
    public TokenAuthenticationFilter(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (StringUtils.hasLength(token)) {
            String username = JwtHelper.getUsername(token);
            if (StringUtils.hasLength(username)) {
                //通过username从redis获取权限数据
                String authString = stringRedisTemplate.opsForValue().get(username);
                //把redis获取字符串权限数据转换为集合类型
                if (StringUtils.hasLength(authString)){
                    List<Map> maps = JSON.parseArray(authString, Map.class);
                    ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
                    for (Map map:maps) {
                        authorities.add(new SimpleGrantedAuthority((String) map.get("authority")));

                    }
                    return new UsernamePasswordAuthenticationToken(username, null, authorities);
                }
            }
        }
        return null;
    }
}

修改配置类

 

 修改yml

#redis配置
  redis:
    host: 192.168.200.110
    port: 6379
    database: 0
    timeout: 1800000
    password: 123456
    jedis:
      pool:
        max-active: 20 #最大连接数
        max-wait: -1 #最大阻塞等待时间
        max-idle: 5 #最大空闲数
        min-idle: 0 #最小空闲

在controller层加注解

 异常处理:

org.springframework.security.access.AccessDeniedException: 不允许访问
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:239)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208)
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:58)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)

    /**
     * spring security异常
     * @param e
     * @return
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseBody
    public Result error(AccessDeniedException e) throws AccessDeniedException {
        return Result.fail().code(205).message("没有操作权限");
    }

引入pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <scope>provided</scope>
</dependency>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/561571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一、尚医通平台前端搭建

文章目录 一、尚医通平台前端搭建1、服务端渲染技术NUXT 二、首页实现1、公共处理1.1添加静态资源1.2 定义布局1.2.1 修改默认布局1.2.2 提取头文件1.2.3 提取尾文件1.2.4 默认布局引入头尾文件 2、首页引入2.1 引入首页静态页面2.2 首页数据分析 3、首页数据api接口3.1 医院分…

chatgpt赋能Python-python_penup怎么用

Python Penup - 内在交互性的有用工具 Python编程语言的流行一直与其灵活性和易于使用性息息相关。除此之外&#xff0c;Python还提供了大量的扩展和库&#xff0c;以满足各种编程需求。Penup是Python编程中一个非常有用的工具。 什么是Python Penup&#xff1f; Penup是Pyt…

windows环境下安装RabbitMQ(超详细),

windows环境下安装RabbitMQ&#xff08;超详细&#xff09; 注&#xff1a;安装路径&#xff0c;用户名均为英文 一、RabbitMq简介 1.1消息队列中间件简介 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题…

Discourse 如何配置 MAXMIND 来对 IP 地址反向查询

【配置 MAXMIND&#xff0c;Discourse 需要重新构建&#xff0c;这将会导致服务中断。 】 什么是 MAXMIND 和为什么我们需要使用这个服务 Discourse 使用 MAXMIND 来通过 IP 地址反向查询具体的物理地址。 如果 Discourse 没有配置 Maxmind’s 数据库&#xff0c;我们看到的配…

leetcode 数据库题 196,197,262,511,550,570

leetcode 数据库题第二弹 196. 删除重复的电子邮箱197. 上升的温度262. 行程和用户511. 游戏玩法分析 I550. 游戏玩法分析 IV570. 至少有5名直接下属的经理577. 员工奖金小结 196. 删除重复的电子邮箱 题目地址&#xff1a;https://leetcode.cn/problems/delete-duplicate-emai…

二、服务网关-Gateway

文章目录 一、服务网关1、网关介绍2、Spring Cloud Gateway介绍3、搭建server-gateway模块3.1 搭建server-gateway3.2 修改配置pom.xml3.3 在resources下添加配置文件3.4添加启动类3.5 跨域处理3.5.1 为什么有跨域问题&#xff1f;3.5.2解决跨域问题 3.6服务调整3.7测试 一、服…

chatgpt赋能Python-python_pulp包怎么安装

Python Pulp包的安装方法 如果你正在进行线性规划或整数规划问题的研究或解决&#xff0c;那么Python Pulp包是一个非常实用的工具&#xff0c;它可以快速、高效地解决这些问题。但是&#xff0c;在使用Python Pulp包的过程中&#xff0c;你可能会遇到一些安装问题。本文将介绍…

Http协议网络读卡器Aspx网页Request获取刷卡数据Response回应驱动显示

三种提交方式可自由设置 RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) HttpReader.aspx.cs using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebCont…

C/C++ 数据结构设计与应用(四):C++数据压缩与传输:从理论到实践的全景解析

C数据压缩与传输&#xff1a;从理论到实践的全景解析 一、数据压缩的策略与方法 (Strategies and Methods of Data Compression)1.1 数据压缩的基本概念与原理 (Basic Concepts and Principles of Data Compression)1.2 C中的数据压缩技术 (Data Compression Techniques in C)1…

总结vue3 的一些知识点

vue3 支持 jsx 安装依赖 pnpm add vitejs/plugin-vue-jsxvite.config.ts 中引用插件 import { defineConfig } from "vite" import vue from "vitejs/plugin-vue" import vueJsx from "vitejs/plugin-vue-jsx" // https://vitejs.dev/config/ex…

Golang每日一练(leetDay0075) 打家劫舍II、最短回文串

目录 213. 打家劫舍 II House Robber ii &#x1f31f;&#x1f31f; 214. 最短回文串 Shortest Palindrome &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C…

android App外包开发技术难点

在开发android App时需要注意一些技术问题&#xff0c;包括设备兼容、性能优化、安全问题及用户体验&#xff0c;这些问题往往是android App的通用问题&#xff0c;每个android App的项目都有这些问题&#xff0c;今天和大家分享一下这方面的知识&#xff0c;希望对大家有所帮助…

研发工程师玩转Kubernetes——使用环境变量给容器中程序传递参数

在《研发工程师玩转Kubernetes——构建、推送自定义镜像》中&#xff0c;Pod的IP是通过代码获取的 def get_ip():try:s socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.connect((8.8.8.8, 80))ip s.getsockname()[0]finally:s.close()return ip实际我们可以在清单文件中…

python 正则表达式使用总结

re库使用 re.findall import re## 存在匹配的 txt "ai aiThe rain in Spain" x re.findall("ai", txt) print(x) # 没有匹配的 txt "adafda dafasdf" x re.findall("ai", txt) print(x)s中国人adfadsfasfasdfsdaf中国万岁\n pri…

chatgpt赋能Python-python_pythonw

Python和Pythonw——SEO助您成功 Python和Pythonw是两个最广泛使用的编程语言之一。不仅是它们提供了丰富的编程功能和轻松上手的学习曲线&#xff0c;它们还具有出色的SEO优化功能&#xff0c;因为它们是自由和开源的。 Python是什么&#xff1f; Python是一种高级编程语言…

SpringCloud微服务踩坑系列:参数不全造成的空指针

服务一直报500错误&#xff0c;看了一下后台出现了空指针异常 异常如下&#xff1a; java.lang.NullPointerException: null at com.cyf.serviceOrder.service.OrderInfoService.add(OrderInfoService.java:93) ~[classes/:na] at com.cyf.serviceOrder.controller.Or…

chatgpt赋能Python-python_qq群

Python QQ群&#xff1a;学习Python的最佳社区 Python是一个非常流行的编程语言&#xff0c;因其简单易学、可读性高等特点&#xff0c;备受开发人员的青睐。为了交流学习Python&#xff0c;越来越多的人开始组建Python QQ群。本文将介绍Python QQ群的意义、特点和如何找到合适…

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-10

深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践-总结-10 虚拟机类加载机制类加载的过程初始化 类加载器类与类加载器双亲委派模型 虚拟机类加载机制 类加载的过程 初始化 类的初始化阶段是类加载过程的最后一个步骤&#xff0c;前几个类的加载动作里&#xff0c;除了…

Unity 新建你的第一个游戏,以及如何按WASD控制角色运动 (Unity Demo2D)

文章目录 初始化项目新建角色物体游戏资源管理试着导入资源试着管理资源试着使用资源 脚本是啥新建脚本编辑脚本行为逻辑按键检测获取按键移动位置★ 最终代码 (有基础请直接跳到这) 初始化项目 当你打开 Unity Hub&#xff0c;初始化一个 2D 项目&#xff0c;进入了 Unity 编…

人工智能TensorFlow MNIST手写数字识别——训练篇

上期我们分享了CNN的基本结构,本期我们就拿MNIST数据集来训练一下手写数字的数据库,以便我们下期能够使用训练好的模型,来进行手写数字的识别。 分享一下几个可视化网站,可以看到神经网络的识别过程。 http://scs.ryerson.ca/~aharley/vis/conv/ 1、插入MNIST数据集 #利…