1、前置路由守卫
前置路由守卫:就是在路由跳转前加上自己的一些业务代码,未登录之前输入其他路径,不放行,跳转至登录页面。
在main.js文件中加入以下代码
//把axios挂载到vue对象中,以后在vue中如果使用axios直接可以使用$http名称
Vue.prototype.$http = axios
//设置前置路由守卫 to:表示跳转至哪个路由,from:从哪个路由来,next:放行到指定路由
router.beforeEach((to, from, next) => {
//获得跳转的路径
var path = to.path;
//是否为登录路由路径
console.log(path==="/login");
if (path==="/login"){
//放行
return next();
}
//其他路由路径,判断是否登录
var token = sessionStorage.getItem("token");
if (token){
return next();
}
//跳转至登录
return next("/login");
console.log("这里是前置路由守卫")
})
2、整合shiro安全框架
2.1 依赖
<!--shiro整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
2.2 shiro配置类
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
@Bean
public Realm realm(){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(credentialsMatcher());
return myRealm;
}
@Bean
public CredentialsMatcher credentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
@Bean(value = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
Map<String, String> map = new HashMap<>();
map.put("/login","anon");
map.put("/doc.html","anon");
map.put("/swagger-ui.html", "anon");
map.put("/webjars/**", "anon");
map.put("/swagger-resources/**", "anon");
map.put("/swagger/**", "anon");
map.put("/swagger2/**", "anon");
map.put("/v2/**", "anon");
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
Map<String, javax.servlet.Filter> filterMap = new HashMap<>();
filterMap.put("authc",new LoginFilter());
shiroFilterFactoryBean.setFilters(filterMap);
return shiroFilterFactoryBean;
}
@Bean
public FilterRegistrationBean<javax.servlet.Filter> filterRegistrationBean(){
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setName("shiroFilter");
registrationBean.setFilter(new DelegatingFilterProxy());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
2.3 登录过滤器
public class LoginFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("未登录执行");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
ObjectMapper objectMapper = new ObjectMapper();
CommonResult commonResult = new CommonResult(5001, "未登录", null);
String value = objectMapper.writeValueAsString(commonResult);
writer.println(value);
writer.flush();
writer.close();
return false;
}
}
2.4 增加一个realm类
public class MyRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User primaryPrincipal = (User) principals.getPrimaryPrincipal();
List permission = userService.selectPermissionByUserId(primaryPrincipal.getId());
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permission);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//根据token获取账号
String username = (String) token.getPrincipal();
//根据账号查询信息
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",username);
userQueryWrapper.eq("is_deleted",0);
User user = userService.getOne(userQueryWrapper);
if (user!=null){
//设置加密时的盐
ByteSource source = ByteSource.Util.bytes(user.getSalt());
//从数据库中获取密码
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), source, this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
2.5 修改controller层代码
@RestController
@Api("登录的接口类")
//@CrossOrigin
public class LoginController {
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("/login")
@ApiOperation("登录接口")
public CommonResult login(@RequestBody LoginVO loginVO){
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginVO.getUsername(), loginVO.getPassword());
subject.login(usernamePasswordToken);
Object principal = subject.getPrincipal();
if (principal!=null){
String token = UUID.randomUUID().toString();
ValueOperations forValue = redisTemplate.opsForValue();
forValue.set(token,principal,24, TimeUnit.HOURS);
return new CommonResult(2000,"登录成功",token);
}
} catch (AuthenticationException e) {
e.printStackTrace();
}
return new CommonResult(5000,"登录失败",null);
}
}
2.6 测试登录
我们登录成功会出现跨域请求的问题,使用f12检查时发现了以下错误。
之前没有整合shiro的时候我们解决了跨域问题,现在出现这个错误的原因是,我们前端服务器向后端发送请求时,options请求和真实的请求都被shiro拦截器给拦截了。
原因:浏览器会在发送真正请求之前,先发送一个方法为OPTIONS的预检请求 Preflighted requests 这个请求是用来验证本次请求是否安全的,而且并不是所有请求都会发送,需要符合以下条件:
- 请求方法不是GET/HEAD/POST
- POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
- 请求设置了自定义的header字段
后端使用了shiro安全框架,每次请求需要在header中携带自定义的字段(Authorization),所以浏览器会多发送一个OPTIONS请求,但是OPTIONS请求不会携带Authorization,后端验证不通,所以会产生跨域问题。
解决问题关键点:使用拦截器解决跨域问题,并且针对OPTIONS请求做放行处理
//如果类没有交与spring容器管理,那么类中的属性也不能交与spring容器管理
public class LoginFilter extends FormAuthenticationFilter {
RedisTemplate redisTemplate;
public LoginFilter(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
System.out.println(redisTemplate);
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String method = httpServletRequest.getMethod();
if (method!=null && method.equals("OPTIONS")){
System.out.println("放行options");
return true;
}
String token = httpServletRequest.getHeader("token");
if (token!=null && redisTemplate.hasKey(token)){
System.out.println("登录放行");
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("未登录执行");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
ObjectMapper objectMapper = new ObjectMapper();
CommonResult commonResult = new CommonResult(5001, "未登录", null);
String value = objectMapper.writeValueAsString(commonResult);
writer.println(value);
writer.flush();
writer.close();
return false;
}
}
注意:如果当前类没有交与spring容器管理,那么这个类中的属性也不能交与spring容器管理
3、主页的布局
<template>
<!---->
<div id="app">
<el-container class="operation-wrapper">
<el-header class="el-header">
<img src="../assets/images/logo.png" style="height:60px;margin-left: 30px;">
<el-dropdown @command="handleCommand" style="position:relative;float: right;height: 60px;line-height: 60px;">
<span class="el-dropdown-link" style="margin-right: 10px;">
<el-avatar :src="userInfo.avatar" style="margin-top: 10px;"></el-avatar>
<span id="userInfo_username" style="display: inline-block;vertical-align: top;color: darkgray" ></span>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="userInfo">个人信息</el-dropdown-item>
<el-dropdown-item command="changePwd">修改密码</el-dropdown-item>
<el-dropdown-item command="exit">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
<el-container style="height: 100%">
<el-aside class="el-aside" width="230px">
</el-aside>
<el-main class="el-main">
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "Home",
data(){
return {
//个人信息
userInfo:{},
//个人信息悬浮框数据表格
userForm:{},
//个人信息悬浮框显示控制变量
userFormVisible:false,
//个人信息悬浮窗数据显示宽度
formLabelWidth:"80px",
//左侧菜单列表
leftMenu:[],
}
},
created() {
this.getUserInfo();
},
methods: {
getUserInfo(){
this.$http.get("http://localhost:8081/user/userInfo").then(result=>{
if (result.data.code===2000){
this.userInfo=result.data.data;
console.log(this.userInfo.username)
document.getElementById('userInfo_username').innerText=this.userInfo.username;
}
});
},
handleCommand(command){
},
}
}
</script>
<style>
body{
margin: 0px;
padding: 0px;
height: 100%;
}
#app{
height: 100%;
}
.el-header {
background-color: white;
color: #333;
height: 60px;
line-height: 60px;
}
.el-aside {
background-color: #26333E;
color: #333;
height: calc(100vh - 61px); /*61px为顶部header盒子高度*/
overflow-y: auto;
}
.el-aside>.el-menu{
border: 0px;
}
.el-main {
background-color:#F2F2F2;
color: #333;
text-align: center;
padding: 0px 16px !important;
height: calc(100vh - 61px); /*61px为顶部header盒子高度*/
overflow-y: auto;
}
body > .el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 320px;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
4、退出功能
4.1 设置axios的基础路径
//设置axios的基础路径
axios.defaults.baseURL="http:localhost:8081";
4.2 前端
handleCommand(command){
if (command=="exit"){
axios.get("/exit").then(result=>{
if (result.data.code==2000){
this.$message.success(result.data.msg);
sessionStorage.clear();
this.$router.push("/login")
}
})
}
},
4.3 后端
@GetMapping("/exit")
public CommonResult exit(HttpServletRequest request){
String token = request.getHeader("token");
if (redisTemplate.hasKey(token)){
redisTemplate.delete(token);
return new CommonResult(2000,"退出成功",null);
}
return new CommonResult(5000,"无效的token",null);
}
5、获取左侧菜单
5.1 前端
//左侧菜单栏
initLeftMenu(){
this.$http.get("/permission/leftMenu").then(result=>{
if (result.data.code===2000){
this.leftMenu = result.data.data;
}
})
},
5.2 controller层
@RestController
@RequestMapping("/permission")
public class PermissionController {
@Autowired
private IPermissionService iPermissionService;
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/leftMenu")
public CommonResult leftMenu(HttpServletRequest request){
String token = request.getHeader("token");
ValueOperations valueOperations = redisTemplate.opsForValue();
User user = (User) valueOperations.get(token);
String id = user.getId();
List list = iPermissionService.selectPermissionByUserId(id);
if (list.size()>0){
return new CommonResult(2000,"左侧菜单查询成功",list);
}
return new CommonResult(5000,"查询失败",null);
}
}
5.3 Permission.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.gjx.mapper.PermissionMapper">
<select id="selectPermissionByUserId" resultType="com.gjx.entity.Permission">
select DISTINCT p.* from acl_user_role ur join acl_role_permission rp
on ur.role_id=rp.role_id join acl_permission p
on rp.permission_id=p.id where ur.user_id=#{id} and type=1
</select>
</mapper>
5.4 service的实现类
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements IPermissionService {
@Autowired
private PermissionMapper permissionMapper;
@Override
public List selectPermissionByUserId(String id) {
List<Permission> permissions = permissionMapper.selectPermissionByUserId(id);
ArrayList<Permission> firstMenus = new ArrayList<>();
for (Permission p:permissions) {
if (p.getPid().equals("1")){
firstMenus.add(p);
}
}
//为一级菜单设置二级菜单
for (Permission first:firstMenus) {
//根据一级菜单id 查询 该菜单得二级菜单。如果出现不确定有几级菜单 那么我们可以使用方法得递归调用
first.setChildren(findChildren(permissions,first.getId()));
}
return firstMenus;
}
//递归方法
List<Permission> findChildren(List<Permission> permissions,String id){
ArrayList<Permission> children = new ArrayList<>();
for (Permission p : permissions) {
if (p.getPid().equals(id)){
children.add(p);
}
}
for (Permission child:children){
child.setChildren(findChildren(permissions,child.getId()));
}
return children;
}
}
5.5 前端根据返回结果显示层级关系
<el-aside class="el-aside" width="230px">
<el-menu
class="el-menu-vertical-demo"
background-color="#26333E"
text-color="#fff"
active-text-color="#0089A7"
unique-opened>
<el-submenu :index="firstMenu.id+''" v-for="firstMenu in leftMenu">
<template slot="title">
<i :class="firstMenu.icon"></i>
<span>{{firstMenu.name}}</span>
</template>
<el-menu-item
:index="secondMenu.id+''"
v-for="secondMenu in firstMenu.children"
@click="addTab(secondMenu.path,secondMenu.name)"
>
<i :class="secondMenu.icon"></i>
<span slot="title">{{secondMenu.name}}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
本文含有隐藏内容,请 开通VIP 后查看