JAVAEE细细看 框架23 - Spring Security集成

Spring Security

01 - 扫描包

修改工程中的springmvc.xml文件,修改dubbo批量扫描的包
路径

<!‐‐批量扫描‐‐>
<dubbo:annotation package="com.ittest" />

02 - 引入配置文件

在springmvc.xml中添加

 <import resource="spring-security.xml"></import>

在工程中提供spring-security.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
						http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/mvc
						http://www.springframework.org/schema/mvc/spring-mvc.xsd
						http://code.alibabatech.com/schema/dubbo
						http://code.alibabatech.com/schema/dubbo/dubbo.xsd
						http://www.springframework.org/schema/context
						http://www.springframework.org/schema/context/spring-context.xsd
                     http://www.springframework.org/schema/security
                     http://www.springframework.org/schema/security/spring-security.xsd">

    <!--配置哪些资源匿名可以访问(不登录也可以访问)-->
    <!--<security:http security="none" pattern="/pages/a.html"></security:http>
    <security:http security="none" pattern="/pages/b.html"></security:http>-->
    <!--<security:http security="none" pattern="/pages/**"></security:http>-->
    <security:http security="none" pattern="/login.html"></security:http>
    <security:http security="none" pattern="/css/**"></security:http>
    <security:http security="none" pattern="/img/**"></security:http>
    <security:http security="none" pattern="/js/**"></security:http>
    <security:http security="none" pattern="/plugins/**"></security:http>
    <!--
        auto-config:自动配置,如果设置为true,表示自动应用一些默认配置,比如框架会提供一个默认的登录页面
        use-expressions:是否使用spring security提供的表达式来描述权限
    -->
    <security:http auto-config="true" use-expressions="true">
        <security:headers>
            <!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问
								DENY:浏览器拒绝当前页面加载任何Frame页面
    						SAMEORIGIN:frame页面的地址只能为同源域名下的页面
    						ALLOW-FROM:origin为允许frame加载的页面地址。
						-->
            <security:frame-options policy="SAMEORIGIN"></security:frame-options>
        </security:headers>
        <!--配置拦截规则,/** 表示拦截所有请求-->
        <!--
            pattern:描述拦截规则
            asscess:指定所需的访问角色或者访问权限
        -->
        <!--只要认证通过就可以访问-->
        <security:intercept-url pattern="/pages/**"  access="isAuthenticated()" />
        
        <!--如果我们要使用自己指定的页面作为登录页面,必须配置登录表单.页面提交的登录表单请求是由框架负责处理-->
        <!--
            login-page:指定登录页面访问URL
        -->
        <security:form-login
                login-page="/login.html"
                username-parameter="username"
                password-parameter="password"
                login-processing-url="/login.do"
                default-target-url="/pages/main.html"
                authentication-failure-url="/login.html"></security:form-login>

        <!--
          csrf:对应CsrfFilter过滤器
          disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
        -->
        <security:csrf disabled="true"></security:csrf>

        <!--
          logout:退出登录
          logout-url:退出登录操作对应的请求路径
          logout-success-url:退出登录后的跳转页面
        -->
        <security:logout logout-url="/logout.do"
                         logout-success-url="/login.html" invalidate-session="true"/>

    </security:http>

    <!--配置认证管理器-->
    <security:authentication-manager>
        <!--配置认证提供者-->
        <security:authentication-provider user-service-ref="springSecurityUserService">
            <!--
                    配置一个具体的用户,后期需要从数据库查询用户
            <security:user-service>
                <security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
            </security:user-service>
            -->
            <!--指定度密码进行加密的对象-->
            <security:password-encoder ref="passwordEncoder"></security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>

    <!--配置密码加密对象-->
    <bean id="passwordEncoder"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

    <!--开启注解方式权限控制-->
    <security:global-method-security pre-post-annotations="enabled" />
</beans>

以下资源需要放行:

	<security:http security="none" pattern="/login.html"></security:http>
   <security:http security="none" pattern="/css/**"></security:http>
   <security:http security="none" pattern="/img/**"></security:http>
   <security:http security="none" pattern="/js/**"></security:http>
   <security:http security="none" pattern="/plugins/**"></security:http>

所有页面认证通过就能访问,细粒度的权限控制在接口层controller中通过注解方式做:

		 <!--只要认证通过就可以访问-->
        <security:intercept-url pattern="/pages/**"  access="isAuthenticated()" />

登录完之后的首页更改为:

/pages/main.html

认证管理器就不需要注入bean了,只需要进行应用,名称为类名首字母小写

 <security:authentication-provider user-service-ref="springSecurityUserService">

03 - iframe放行

数据库中的密码有误,admin密码应该为:

admin/admin

$2a 10 10 10QjWSVpt35GMwq9sPSb6beu2Ctp5o5iQyZ/kbpHJlgkA.rpkEqtfLS

在<security:http auto-config=“true” use-expressions=“true”>中添加

<security:headers>
            <!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
            <security:frame-options policy="SAMEORIGIN"></security:frame-options>
        </security:headers>

由于页面框架右边是使用iframe进行内嵌页面展示,需要添加策略,默认为DENY

DENY:浏览器拒绝当前页面加载任何Frame页面

SAMEORIGIN:frame页面的地址只能为同源域名下的页面

ALLOW-FROM:origin为允许frame加载的页面地址。必须配置strategy属性和value属性。否则项目启动报错。

关闭策略:

<security:frame-options disabled=“true”/>

04 - 测试用 html

处理登录的异常,给用户提示:

<!DOCTYPE html>
<html>
    <head>
        <!-- 页面meta -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title></title>
        <meta name="description" content="">
        <meta name="keywords" content="">
        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
        <!-- 引入样式 -->
        <link rel="stylesheet" href="plugins/elementui/index.css">
        <link rel="stylesheet" href="css/login.css">
        <!-- 引入组件库 -->
        <script src="js/vue.js"></script>
        <script src="plugins/elementui/index.js"></script>
        <script src="js/axios-0.18.0.js"></script>
        <script>
            function getUrlParam(paraName) {
                var url = document.location.toString();
                //alert(url);
                var arrObj = url.split("?");
                if (arrObj.length > 1) {
                    var arrPara = arrObj[1].split("&");
                    var arr;
                    for (var i = 0; i < arrPara.length; i++) {
                        arr = arrPara[i].split("=");
                        if (arr != null && arr[0] == paraName) {
                            return arr[1];
                        }
                    }
                    return "";
                }
                else {
                    return "";
                }
            }
            var param = getUrlParam('error')
            if(param){
                axios.get("/user/loginerror.do").then(res => {
                    alert(res.data)
                })
            }
        </script>
    </head>
    <body class="hold-transition skin-purple sidebar-mini">
        <div id="app">
            <div class="login-container">
                <div class="loginBox">
                  <form method="post" class="login-form" action="/login.do" label-position="left">
                      <div class="title-container">
                        <div class="logoInfo clearfix">
                          <em class="logo"></em>
                        </div>
                      </div>
                      <div>
                        <span class="svg-container svg-container_login">
                          <span class="user"></span>
                        </span>
                        <input type="text" name="username" placeholder="请输入用户名" />
                      </div>
                      <div>
                        <span class="svg-container">
                          <span class="username"></span>
                        </span>
                        <input type="password" name="password" placeholder="请输入密码"/>
                      </div>
                      <input type="submit" style="width:100%;margin-bottom:30px;" value="登录"></input>
                </form>
                </div>
              </div>
        </div>
    </body>

</html>

service:

package com.ittest.service;

import com.alibaba.dubbo.config.annotation.Reference;
import com.ittest.backend.pojo.UserEnhancher;
import com.ittest.exception.MyUsernameNotFoundException;
import com.ittest.pojo.Permission;
import com.ittest.pojo.Role;
import com.ittest.pojo.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class SpringSecurityUserService implements UserDetailsService {
    //使用dubbo通过网络远程调用服务提供方获取数据库中的用户信息
    @Reference
    private UserService userService;

    //根据用户名查询数据库获取用户信息
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.findByUsername(username);
        if(user == null){
            //用户名不存在;
            throw new MyUsernameNotFoundException("用户名不存在");
        }

        List<GrantedAuthority> list = new ArrayList<>();

        //动态为当前用户授权 [1[4,5,6],2[7,8,9],3[10]]
        Set<Role> roles = user.getRoles();
        for (Role role : roles) {
            //遍历角色集合,为用户授予角色ROLE_XXX
            list.add(new SimpleGrantedAuthority(role.getKeyword()));
            Set<Permission> permissions = role.getPermissions();
            for (Permission permission : permissions) {
                //遍历权限集合,为用户授权 CHECKITEM_ADD
                list.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        //org.springframework.security.core.userdetails.User
        User securityUser = new User(username,user.getPassword(),list);
        return securityUser;
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        System.out.println(encoder.encode("admin"));
    }
}

异常:

package com.ittest.exception;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class MyUsernameNotFoundException extends AuthenticationException {
    private static final long serialVersionUID = 1L;

    public MyUsernameNotFoundException(String msg) {
        super(msg);
    }

    public MyUsernameNotFoundException(String msg, Throwable t) {
        super(msg, t);
    }
}

controller:

package com.ittest.controller;

import com.ittest.backend.pojo.UserEnhancher;
import com.ittest.constant.MessageConstant;
import com.ittest.entity.Result;
import com.ittest.exception.MyUsernameNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 用户操作
 */
@RestController
@RequestMapping("/user")
public class UserController {
    //获得当前登录用户的用户名
    @RequestMapping("/getUsername")
    public Result getUsername(HttpServletRequest request){
        //当Spring security完成认证后,会将当前用户信息保存到框架提供的上下文对象
        request.getHeader("AuthToken");
        UserEnhancher user = (UserEnhancher) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println(user);
        if(user != null){
            String username = user.getRealname();
            return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,username);
        }

        return new Result(false, MessageConstant.GET_USERNAME_FAIL);
    }

    //获得错误信息
    @RequestMapping("/loginerror")
    public String loginerror(HttpServletRequest request, HttpServletResponse response){
        HttpSession session = request.getSession();
        AuthenticationException spring_security_last_exception = (AuthenticationException)session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");

        /**
         * 用户名不存在:UsernameNotFoundException;
         * 密码错误:BadCredentialException;
         * 帐户被锁:LockedException;
         * 帐户未启动:DisabledException;
         * 密码过期:CredentialExpiredException;
         */
        if(spring_security_last_exception instanceof BadCredentialsException){
            return "密码错误";
        }

        return spring_security_last_exception.getMessage();
    }
}

05 - 接口权限控制

在CheckItemController中更改代码:

    //删除检查项
    @PreAuthorize("hasAuthority('CHECKITEM_DELETE')")//权限校验
    @RequestMapping("/delete")
    public Result delete(Integer id){
        try{
            checkItemService.deleteById(id);
        }catch (Exception e){
            e.printStackTrace();
            //服务调用失败
            return new Result(false, MessageConstant.DELETE_CHECKITEM_FAIL);
        }
        return  new Result(true, MessageConstant.DELETE_CHECKITEM_SUCCESS);
    }

在数据库中将t_role_permission表中role_id是2,permission_id是2的数据删除

使用admin/admin登录之后,删除检查项时不会报没有权限的提示

使用xiaoming/1234登录,删除检查项会没有提示,因为我们还没有把没有权限的错误返回到前端进行展示

06 - html 设置

返回非200的返回码时,需要进行catch处理

             // 删除
                handleDelete(row) {//row其实是一个json对象,json对象的结构为{"age":"0-100","attention":"无","code":"0011","id":38,"name":"白细胞计数","price":10.0,"remark":"白细胞计数","sex":"0","type":"2"}
                    //alert(row.id);
                    this.$confirm("你确定要删除当前数据吗?","提示",{//确认框
                        type:'warning'
                    }).then(()=>{
                        //用户点击确定按钮,发送ajax请求,将检查项ID提交到Controller进行处理
                        axios.get("/checkitem/delete.do?id=" + row.id).then((res) => {
                            if(res.data.flag){
                                //执行成功
                                //弹出成功提示信息
                                this.$message({
                                    type:'success',
                                    message:res.data.message
                                });
                                //重新进行分页查询
                                this.findPage();
                            }else{
                                //执行失败
                                this.$message.error(res.data.message);
                            }
                        }).catch((r)=>{
                            this.showMessage(r);
                        });
                    }).catch(()=>{
                        this.$message({
                            type:'info',
                            message:'操作已取消'
                        });
                    });
                }

我们可以封装一个showMessage方法进行复用

 				showMessage(r){
                    if(r == 'Error: Request failed with status code 403'){
                        //权限不足
                        this.$message.error('无访问权限');
                        return;
                    }else{
                        this.$message.error('未知错误');
                        return;
                    }
                },

备注:其实这种方式不是特别规范,如果将来spring security框架返回的信息变化了,就无法触发无访问权限。

			showMessage(r){
                    if(r.response.status == 403){
                        //权限不足
                        this.$message.error('无访问权限');
                        return;
                    }else{
                        this.$message.error('未知错误');
                        return;
                    }
                },

使用http返回码进行判断,如果是403就代表无访问权限

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值