前后端分离的项目整合shiro安全框架

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 依赖



    org.apache.shiro
    shiro-spring-boot-starter
    1.7.0

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 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 filterMap = new HashMap<>();
        filterMap.put("authc",new LoginFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean 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 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 这个请求是用来验证本次请求是否安全的,而且并不是所有请求都会发送,需要符合以下条件:

  1. 请求方法不是GET/HEAD/POST
  2. POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
  3. 请求设置了自定义的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;
    }
}

前后端分离的项目整合shiro安全框架_第1张图片

前后端分离的项目整合shiro安全框架_第2张图片

注意:如果当前类没有交与spring容器管理,那么这个类中的属性也不能交与spring容器管理

3、主页的布局





前后端分离的项目整合shiro安全框架_第3张图片

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文件





    

5.4 service的实现类

@Service
public class PermissionServiceImpl extends ServiceImpl implements IPermissionService {

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List selectPermissionByUserId(String id) {
        List permissions = permissionMapper.selectPermissionByUserId(id);
        ArrayList 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 findChildren(List permissions,String id){
        ArrayList 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 前端根据返回结果显示层级关系


    
        
            
            
                
                {{secondMenu.name}}
            
        
    

前后端分离的项目整合shiro安全框架_第4张图片

 前后端分离的项目整合shiro安全框架_第5张图片

你可能感兴趣的:(Java高级,前端,安全,vue.js,javascript)