目录
一、权限管理
二、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、用户认证
用户认证流程:
自定义加密器处理组件:
@Componentpublic 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;
}@Servicepublic 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>