您的位置:首页 > 编程语言 > Java开发

Java,SpringSecurity安全框架的简单使用及如何配置文件,权限角色管理,登录失败处理器,登录成功处理器,BCrypt加密法,权限控制注解,权限角色控制表

2019-04-24 23:33 911 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/yan95520/article/details/89504449

本文非常非常长,希望大家有足够时间及耐心阅读,直接复制代码跑通程序大概也得半个钟,框架使用简单,配置多

废话不说,直接做,做完就懂了,不懂再做一遍,培养动手能力

一、导入jar包(此处只导入Spring security相关jar包)   本次测试使用SSM框架测试

<spring.version>5.0.10.RELEASE</spring.version>

 

<dependency>

    <groupId>org.springframework.security</groupId>

    <artifactId>spring-security-web</artifactId>

    <version>${spring.version}</version>

</dependency>

<dependency>

    <groupId>org.springframework.security</groupId>

    <artifactId>spring-security-config</artifactId>

    <version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-taglibs</artifactId>

<version>${spring.version}</version>

</dependency>

二、配置web.xml:过滤器最好放在其他过滤器的最前面
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

三、创建spring-security.xml(需要在applicationContext.xml文件中导入),

配置文件中多处引用我们自己创建的类,后续有图描述,可以直接下拉到第十二个标题,现在位置是第三个标题

导入语句:<import resource="spring-security.xml"/>   <!-- 漏导入会报NoBeanspringSecurityFilterChain这个错误 -->

<?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:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-4.2.xsd ">
    <!--扫描-->
    <context:component-scan base-package="com.it.security"/>
    <!--允许代码上使用注解(3种类型都可以,我们常用的是pre-post-annotations),
    由于这个文件导入父容器,所以springmvc可以使用父容器的配置,会自动扫描controller上的注解-->
    <sec:global-method-security
            secured-annotations="enabled" jsr250-annotations="enabled" pre-post-annotations="enabled"/>


    <!-- 静态资源,不用权限 -->
    <sec:http pattern="/resources/**" security="none"/>
    <sec:http pattern="/verify/**" security="none"/>
    <sec:http pattern="/login" security="none"/>
    <sec:http pattern="/user/register.*" security="none"/>
    <sec:http pattern="/favicon.ico" security="none"/>
    <!--<sec:http pattern="/logindo" security="none"/>注意千万不要在静态资源这里设置登录提交网址,会让请求无法进入spring security体系-->

    <!--这里设置请求,包括路径拦截,登录,登出配置等-->
    <sec:http use-expressions="true" auto-config="true" entry-point-ref="authenticationProcessingFilterEntryPoint">
        <sec:headers>
            <sec:frame-options policy="SAMEORIGIN"/>
        </sec:headers>

        <!--路径拦截设置-->
        <sec:intercept-url pattern="/**" access="permitAll"/>
        <!--登录参数设置-->
        <!--
            

1.登陆失败跳转页面
            2.form表单的账号标签name
            3.form表单的密码标签name
            4.form表单提交的请求url,一定要与html标签的值一致,如果控制器有写RequestMapping注解,请填上
            5.登陆成功后跳转的页面
            6.登陆完后是否跳转到target-url,false代表调回上一个页面

           7.自定义登录成功类

           8.自定义登录失败类

            定义7和8 之后,5和6就废了,可以删除亦可保留


        -->


        <sec:form-login login-page="/member/login"
                        username-parameter="loginname"
                        password-parameter="loginpwd"
                        login-processing-url="/member/yy"
                        default-target-url="/member/my"
                        always-use-default-target="false"
                        authentication-success-handler-ref="success_handler"
                        authentication-failure-handler-ref="failure_handler"
        />

        <!--记住我-方式1:md5加密    参考链接: https://blog.csdn.net/yan95520/article/details/89365056-->
        <!--<sec:remember-me key="mykey" user-service-ref="myuserdetailservice" remember-me-parameter="remember_me"/><!–这个”key”属性一起加密到cookie中用来辨别分辨不同项目的cookie–>-->
        <!--记住我-方式2:数据库-->
        <sec:remember-me data-source-ref="dataSource" token-validity-seconds="1209600" remember-me-parameter="remember_me" />
        <!--数据库添加表:create table PERSISTENT_LOGINS(username VARCHAR(64) not null,series VARCHAR(64) not null,token VARCHAR(64) not null,last_used DATETIME not null);alter table PERSISTENT_LOGINS  add constraint PK_PERSISTENT_LOGIN primary key (series);-->
        <!--登出设置 注销作用域中的数据-->
        <sec:logout invalidate-session="true" logout-url="/logout" logout-success-url="/member/login"/>
        <!--暂时禁用csrf安全保护-->
        <sec:csrf disabled="true"/>
    </sec:http>

    <!--指定验证管理器采用数据库验证-->
    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider ref="daoAuthenticationProvider" />
    </sec:authentication-manager>

    <!--数据库验证,配置我们的子类进去-->
    <bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <!--数据库登录验证使用哪个子类-->
        <property name="userDetailsService" ref="myuserdetailservice"/>
        <!-- 是否隐藏用户没有找到的异常,默认为true,即将不能准确地报告用户是否存在的异常 -->
        <property name="hideUserNotFoundExceptions" value="false" />
        <!--数据库密码设置子类-->
        <property name="passwordEncoder" ref="encoder2"/>
        <!-- <beans:property name="saltSource" ref="saltSource"/> 加密盐值-->
    </bean>

    <!--<beans:bean id="md5Encoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />-->
    <bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <constructor-arg name="loginFormUrl" value="/member/login" />
    </bean>
    <!--新的加密算法-->
    <bean id="encoder2"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
        <constructor-arg name="strength" value="9" />
    </bean>
</beans>

四、为权限控制设计数据表

#用户表

drop table if exists `user`;

create table `user`(

`user_id` int PRIMARY KEY

,`user_nickname` varchar(30) not null

,`user_loginname` varchar(30) not null

,`user_loginpwd` varchar(30) not null

 

);

 

#角色表

create table sec_role

(

   roleid int auto_increment primary key not null,

   rolename varchar(20) not null

);

 

insert into sec_role values(1,'SuperAdmin');

insert into sec_role values(2,'Admin');

insert into sec_role values(3,'Manager');

insert into sec_role values(4,'Group');

insert into sec_role values(5,'Emp');

 

#功能表(如查看列表)

create table sec_permission

(

   pmsid int auto_increment primary key not null,

   pmsname varchar(20),

   pmsdesc varchar(20)

);

insert into sec_permission values(1,'checkIn','签到');

insert into sec_permission values(2,'checkOut','签退');

insert into sec_permission values(3,'checkin:list:all','查看考勤列表-全部');

insert into sec_permission values(4,'checkin:list:dept','查看考勤列表-部门');

insert into sec_permission values(5,'checkin:list:my','查看考勤列表-个人');

 

#用户角色表

create table sec_user_role

(

  urid int auto_increment primary key not null,

  userid int  references users(user_id),

  roleid int  references sec_role(roleid)

);

 

insert into sec_user_role values(1,1,2);

insert into sec_user_role values(2,1,5);

 

 

#角色权限表

create table sec_role_permission

(

  rpid int auto_increment primary key not null,

  roleid int  references sec_role(roleid),

  pmsid int references sec_permission(pmsid)

);

 

insert into sec_role_permission values(1,2,3);

insert into sec_role_permission values(2,2,4);

insert into sec_role_permission values(3,2,6);

insert into sec_role_permission values(4,2,10);

insert into sec_role_permission values(5,3,4);

insert into sec_role_permission values(6,3,9);

insert into sec_role_permission values(7,5,1);

insert into sec_role_permission values(8,5,2);

insert into sec_role_permission values(9,5,5);

insert into sec_role_permission values(10,5,8);

 

 

#额外授权表(添加其他角色拥有的某一功能)

create table sec_user_add_permission

(

  upid int auto_increment primary key not null,

  userid int references users(user_id),

  pmsid int  references sec_permission(pmsid)

);

insert into sec_user_add_permission values(1,4,4);

 

#创建视图的用途是,java中编写select连表查询比较繁琐,直接使用视图查询出所需的功能集合和角色集合

create view v_user_permission as

  (select distinct(p.pmsname),u.user_id from users u

  join sec_user_role ur on u.user_id=ur.userid

  join sec_role r on r.roleid=ur.roleid

  join sec_role_permission rp on rp.roleid=r.roleid

  join sec_permission p on p.pmsid=rp.pmsid)

  union

  (select distinct(p.pmsname) as xx,u.user_id from sec_user_add_permission up

  join users u on u.user_id=up.userid

  join sec_permission p on p.pmsid=up.pmsid);

  

create view v_user_role as

select r.rolename,u.user_id from users u

join sec_user_role ur on u.user_id=ur.userid

join sec_role r on r.roleid=ur.roleid ;

 

在Mapper文件中的查询语句

//获取该用户的所有功能

<select id="selectPermissions" resultType="String">
   SELECT pmsname FROM v_user_permission WHERE user_id=#{id}
</select>

//获取该用户的所有角色
<select id="selectRoles" resultType="String">
   SELECT rolename FROM v_user_role WHERE user_id=#{id}
</select>

 

 

五、创建MyUser类,存储用户类的信息及其他(加强版User类),界面层使用的用户类

import com.it.entity.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

//按 spring security标准制造的用户类
public class MyUser implements UserDetails{

    private User user;//User类需要实现序列化
    private Set<String> pmsSet;//功能集合
    private Set<String> roles;//角色集合
    Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();

    public MyUser() {
    }
    public MyUser(User user,Set<String> pmsSet,Set<String> roles) {
        this.user=user;
        this.pmsSet = pmsSet;
        this.roles = roles;
        for(String pms:pmsSet){
            authorities.add(new SimpleGrantedAuthority(pms));
        }
        for(String role:roles){
            authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
        }
    }

    //判断是否拥有此功能
    public boolean hasAuthority(String str){
        for (GrantedAuthority au :this.authorities){
            if(au.getAuthority().equalsIgnoreCase(str)){
                return true;
            }
        }
        return false;
    }

    //权限
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.member.getLoginPwd();
    }

    @Override
    public String getUsername() {
        return this.user.getLoginName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user= user;
    }

    public Set<String> getPmsSet() {
        return pmsSet;
    }

    public void setPmsSet(Set<String> pmsSet) {
        this.pmsSet = pmsSet;
    }

    public Set<String> getRoles() {
        return roles;
    }

    public void setRoles(Set<String> roles) {
        this.roles = roles;
    }
}

 

六、创建密码验证类

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

//对比密码使用的类,如使用BCryptPasswordEncoder加密类,此类失效
@Component("mypasswordencoder")
public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        //加密
        return charSequence.toString();//不加密直接返回
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {

       //可按自己的做法比较密码,如MD5脱盐后再比较
        return charSequence.toString().equalsIgnoreCase(s);//对比密码
    }
}

七、创建查询用户类

import com.it.entity.Cdt;
import com.it.entity.User;
import com.it.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Set;

//用于从数据库加载用户
@Component("myuserdetailservice")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    @Qualifier("userservice")
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user= userService.findObject(Arrays.asList(new Cdt("user_loginname","=",username)));
        if(user== null){throw new UsernameNotFoundException("用户名不存在");}
        Set<String> pmsSet = userService.findPermissions(user.getId());//功能集合
        Set<String> roles = userService.findRoles(user.getId());        //角色集合
        return new MyUser(user,pmsSet,roles);//将用户,功能集合,角色集合塞进MyUser返回出去
    }
}

七、制作SecurityUtil类,方便获取MyUser

import com.it.security.MyUser;
import org.springframework.security.core.context.SecurityContextHolder;

public class MySecurityUtil {

    public static MyUser getCurrentUser(){   //其实MyUser存储在session中,如果你知道key值可以直接获取,手动笑脸
        MyUser myUser = (MyUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return  myUser;
    }
}

八、登陆成功处理器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyPreFilter;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.study.it.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component("success_handler")
public class MyAuthenctiationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功后:"+"自定义MyAuthenctiationSuccessHandler");
        response.setCharacterEncoding("utf-8");//编码不要忘了,有了框架之后很容易忽略

        //判断是重定向还是返回json
        String accept = request.getHeader("Accept");
        if(accept.indexOf("application/json")!=-1){ //ajax请求的时候返回json
            MyUser myUser = (MyUser) authentication.getPrincipal();//获取MyUser类
            //将MyUser类的User里的密码排除后的转成json格式
           SimplePropertyPreFilter filter = new SimplePropertyPreFilter(User.class);
           filter.getExcludes().add("loginPwd");
           String json = JSONObject.toJSONString(myUser.getUser(),new PropertyPreFilter[]{filter});
           response.setContentType("application/json");
           response.getWriter().print(json);
           response.getWriter().close();
       }else {//正常的时候跳转页面
            //方案一:使用request或response跳转页面,可使用servlet基础的知识来做
           // response.sendRedirect(request.getContextPath()+"/user/login");
            //方案二:调用父类方法(onAuthenticationSuccess)跳转 ,注意default-target-url将不起作用
            this.setDefaultTargetUrl("/check/checkin");//如是第一次登陆则跳转此路径
            this.setAlwaysUseDefaultTargetUrl(false);//是否总是跳转到默认页面,fasle:跳转到登陆前一个页面
            super.onAuthenticationSuccess(request,response,authentication);
        }

    }
}

 

九、登陆失败处理器

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component("failure_handler")
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        System.out.println("进入MyAuthenctiationFailureHandler");
        String accept = request.getHeader("Accept");//获取请求头
        if(accept.indexOf("application/json")==-1){//不是json请求
            String message = exception.getMessage().equals("Bad credentials")?"密码错误":exception.getMessage();
            request.setAttribute("err",message);//存储错误信息到作用域中,异常信息包含账户不存在和密码错误信息
            this.setDefaultFailureUrl("/WEB-INF/views/login.jsp");//跳转默认页面,不设报异常'No failure URL set, sending 401 Unauthorized error'
            this.setUseForward(true);//页面跳转是否是内部转发,默认重定向行为
            super.onAuthenticationFailure(request, response,exception);//父类方法完成跳转
            //exception.printStackTrace();
        }else {//ajax方式登录
            response.setContentType("application/json");//设编码
            response.setCharacterEncoding("utf-8");
            //错误信息生成json格式输出
            String json = String.format("{\"my,error\":\"%s\"}", exception.getMessage());
            response.getWriter().print(json);
            response.getWriter().close();
        }

    }
}

 

十、权限控制使用

1.注解

2.Jsp标签:如上图的注解差不多,对比即可了解

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<sec:authorize access="isAuthenticated()" var="islogin">
     <sec:authentication property="principal.user" var="curuser"/>
     欢迎您:<sec:authentication property="principal.username"/>
    </sec:authorize>
    <c:if test="${islogin==false}">
     <a href="${pageContext.request.contextPath}/login">请登录</a>
    </c:if>

 

<sec:authorize access="hasAnyAuthority('ROLE_admin','ROLE_vip','edit_weibo')" var="ispass">
    <a href="${pageContext.request.contextPath}/weibo/edit?id=${w.id}">修改</a>
</sec:authorize>
<c:if test="${ispass!=null&&ispass==false && curuser.id==w.user.id}">
    <a href="${pageContext.request.contextPath}/weibo/edit?id=${w.id}">修改</a>
</c:if>

十一、免登陆

<!--记住我免登陆-方式1:md5加密 remember-me-parameter的值要与表单上勾选框的name值一致-->
<!--这个”key”属性一起加密到cookie中用来辨别分辨不同项目的cookie-->
<sec:remember-me key="mykey" user-service-ref="myuserdetailservice" remember-me-parameter="remember_me"/>


<!--记住我免登陆-方式2:数据库 remember-me-parameter的值要与表单上勾选框的name值一致-->
<sec:remember-me data-source-ref="dataSource" token-validity-seconds="1209600" remember-me-parameter="remember_me" />
<!--数据库添加表:create table PERSISTENT_LOGINS(username VARCHAR(64) not null,series VARCHAR(64) not null,token VARCHAR(64) not null,last_used DATETIME not null);alter table PERSISTENT_LOGINS  add constraint PK_PERSISTENT_LOGIN primary key (series);-->

<input type="checkbox" name="remember_me"  value="1"/>一周免登陆

十二、spring-security.xml中调用的类,备注

 

 

 

 

 

十三、登陆失败处理器中的异常

 

十四、Controller使用表达式总结    有点点乱但难不倒基础扎实的你

@PreAuthorize("isAuthenticated()"):必须是登陆的(自动登陆也算)

@PreAuthorize("isFullyAuthenticated()"):必须是手工登陆的(自动登陆不算)

@PreAuthorize("isRememberMe()"):必须是自动登陆的

@PreAuthorize("hasRole('USER')"):拥有ROLE_USER角色的(ROLE_会被自动加上)

@PreAuthorize("hasAuthority('ROLE_USER')")等同上面

@PreAuthorize("hasAnyRole('USER','VIP')"):拥有ROLE_USER或ROLE_VIP角色的

@PreAuthorize("hasAnyAuthority('ROLE_USER','ROLE_VIP','edit_weibo')"):等同上面

@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')"):拥有ROLE_ADMIN和ROLE_DBA角色的

@PreAuthorize("isFullyAuthenticated() and hasAnyAuthority('ROLE_vip','edit_weibo')")

@PreAuthorize("#contact.name == authentication.name") :参数contact的name属性和当前登录用户的用户名一致

@PreAuthorize("#c.name == authentication.name"):同上,只是起了别名c

public void doSomething(@P("c") Contact contact);

@PreAuthorize("#user.name.equals('abc')"):参数user的name属性是abc

public void add(User user)

@PreAuthorize("principal.username.equals(#username)"):当前登录用户名和参数username相吻合

@PreAuthorize("isFullyAuthenticated() and #uid==authentication.principal.user.id")

public User find(String username)

@PreAuthorize("isFullyAuthenticated()

and hasAnyAuthority('ROLE_vip','edit_weibo')

and #wb.user.id == authentication.principal.user.id"):综合的例子

@PostAuthorize ("returnObject.type == authentication.name"):运行后返回值的type属性和当前登录名一致

@PreFilter(filterTarget="ids", value="filterObject%2==0"):事先过滤ids集合参数,过滤条件是偶数值被使用

public void delete(List<Integer> ids, List<String> usernames)

@PreAuthorize("#id<10")//参数小于10才通过

public User find(int id)

@PostAuthorize("returnObject.id%2==0"):事后返回值的id属性是偶数才允许调用

public User find(int id) {

@PostFilter("filterObject.id%2==0"):返回值被过滤,只有偶数的数据才可返回

public List<User> findAll(){}

 

终于到底部了,是不是很开心,如果你很贪心,觉得这个简单使用不够你胃口,我这里有一遍关于SpringSecurity安全框架更全面的资料,应该足够你看一天的了,待上传................

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐