SpringBoot下整合SpringSecurity实现用户/角色/模块权限,并实现动态权限、基于URL的拦截

2021-07-09 1010点热度 0人点赞 0条评论

本篇介绍如何在SpringBoot下整合SpringSecurity,使用用户角色模块权限模型,自定义处理登录、密码校验,取权限控制,实现用户-模块-角色的权限模型,并基于URL拦截(自行控制AccessDecisionManager,匹配用户所属的角色和请求资源所属的角色,FilterInvocationSecurityMetadataSource,实现请求资源对应的角色)

因为很多地方环节,能实现自定义了,就能整理把握,实现随意的控制和增加自己的业务逻辑。

用户/角色/模块权限模型介绍

这里使用的是RBAC(Role-Based Access Control,基于角色的访问控制),用户通过角色与模块/权限进行关联。某个用户拥有多个角色,每个角色拥有多个模块的权限。这样就能查询出某个用户,所拥有的模块的权限,根据这个来决定该用户是否拥有某个模块的操作权限,如下图所示:

数据库建表

  • 用户表
CREATE TABLE `t_account` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `create_date` datetime DEFAULT NULL COMMENT '新建日期',
  `update_date` datetime DEFAULT NULL COMMENT '更新日期',
  `no` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登录帐号',
  `password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
  `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名',
  `mobile` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手机号',
  `disabled` int NOT NULL DEFAULT '0' COMMENT '是否禁用 0启用 1禁用',
  PRIMARY KEY (`id`),
  UNIQUE KEY `t_account_uk1` (`no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
  • 角色表
CREATE TABLE `t_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
  `disabled` int NOT NULL DEFAULT '0' COMMENT '是否禁用 1禁用',
  `remark` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表';
  • 模块表(权限表)
CREATE TABLE `t_module` (
  `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名称',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'URL',
  `disabled` int NOT NULL DEFAULT '0' COMMENT '是否禁用 1禁用',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='模块(菜单)表';
  • 用户角色授权表
CREATE TABLE `t_role_assign` (
  `id` int NOT NULL AUTO_INCREMENT,
  `account_id` int DEFAULT NULL,
  `role_id` int DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户和角色对应表';
  • 角色模块授权表
CREATE TABLE `t_module_role` (
  `module_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `role_id` int NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='模块(菜单)和角色对应关系表';

pom增加相关依赖

增加spring-boot-starter-security,如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.terrynow</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>test</name>
    <description>test</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.3.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

准备POJO Entity类

Entity类仅供参考,我使用了Hibernate的多对多,其他ORM请修改

Account.java 需要extends SpringSecurity的UserDetails,如下:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @date 2019-06-18 14:31
 * @description
 */
@Entity
@Table(name = "t_account")
public class Account implements Serializable, UserDetails {
    private Long id;
    private String no;
    private String name;
    private String mobile;
    private String password;
    private Date createDate;
    private Date updateDate;
    private boolean disabled;//是否禁用

    private List<Role> roles = new ArrayList<Role>();

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Transient
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
        if (isDisabled()) {
            return authSet;
        }

        //  获取用户对应 角色集合
        List<Role> roles = getRoles();

        //组装成spring需要的格式,其它的就交给spring进行验证
        for (Role role : roles) {
//            System.out.println("取得用户所属的角色:" + role.getName());
            if (!role.isDisabled()) {
                authSet.add(new SimpleGrantedAuthority(String.valueOf(role.getId())));
            }
            //authSet.add(new GrantedAuthorityImpl(role.getId()));
        }
        return authSet;
    }

    public String getPassword() {
        return password;
    }

    @Transient
    @Override
    public String getUsername() {
        return no;
    }

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

    @Transient
    @Override
    public boolean isAccountNonLocked() {
        return !isDisabled();
    }

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

    @Transient
    @Override
    public boolean isEnabled() {
        return !isDisabled();
    }


    public String getNo() {
        return no;
    }

    public void setNo(String no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name = "create_date")
    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    @Column(name = "update_date")
    public Date getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }

    public boolean isDisabled() {
        return disabled;
    }

    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "t_role_assign", joinColumns = @JoinColumn(name = "account_id"), inverseJoinColumns =
    @JoinColumn(name = "role_id"))
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    //以下两行要加,不然spring security不能控制session
    public boolean equals(Object rhs) {
        return rhs instanceof Account && this.no.equals(((Account) rhs).no);
    }

    public int hashCode() {
        return this.no.hashCode();
    }

}

Role.java

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @date 2016-1-7 10:29
 * @description
 */
@Entity
@Table(name = "t_role")
public class Role implements Serializable {
    private Long id;

    private String name;
    private boolean disabled;//是否禁用
    private String remark;//备注

    private List<Account> accounts = new ArrayList<>();
    private List<Module> modules = new ArrayList<Module>();


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ManyToMany(mappedBy = "roles", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    @ManyToMany(mappedBy = "mroles", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    public List<Module> getModules() {
        return modules;
    }

    public void setModules(List<Module> modules) {
        this.modules = modules;
    }

    public boolean isDisabled() {
        return disabled;
    }

    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}

Module.java

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @date 2016-1-7 10:32
 * @description
 */
@Entity
@Table(name = "t_module")
public class Module implements Serializable {
    private String id;
    private String name;
    private String url;
    private boolean disabled;//是否禁用

    private List<Role> mroles = new ArrayList<Role>();

    @Id
    @Column(length = 36)
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "t_module_role", joinColumns = @JoinColumn(name = "module_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    public List<Role> getMroles() {
        return mroles;
    }

    public void setMroles(List<Role> mroles) {
        this.mroles = mroles;
    }

    @Column(name = "url", unique = true)
    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public boolean isDisabled() {
        return disabled;
    }

    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }
}

增加SpringSecurity的配置类

新增一个package,com.terrynow.test.security,新增类:WebSecurityConfig.java

主要是配置了mySecurityMetadataSource(角色提供管理器)、myAccessDecisionManager(决策管理器)、登录校验(myAuthenticationProvider)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;

import javax.sql.DataSource;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2021-7-7 16:21
 * @description
 */
@Configuration
//@EnableWebSecurity(debug = true)
@EnableWebSecurity()
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public static final String SECURITY_KEY = "test";

    @Autowired
    private MyUserDetailService myUserDetailService;

    @Autowired
    private MyLogoutHandler myLogoutHandler;

    @Autowired
    private MyAuthenticationProvider myAuthenticationProvider;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;

    @Autowired
    private MySecurityMetadataSource mySecurityMetadataSource;

    @Bean
    public AuthenticationSuccessHandler myAuthenticationSuccessHandler() {
        return new MyAuthenticationSuccessHandler();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        SessionRegistry sessionRegistry = new SessionRegistryImpl();
        return sessionRegistry;
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return myUserDetailService;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(myAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //防止用户名输入了中文,后面获取到了乱码
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        http.addFilterBefore(filter, CsrfFilter.class);

        //自定义的登录form提交链接,j_spring_security_check是为了和之前SpringWeb的一致,在login的html页面也要一样
        String loginProcessingUrl = "/j_spring_security_check";

        http.headers().frameOptions().disable();//可以嵌套到frame中去
        //login?code=-2发现不能加斜线,要不然如果http://xxx/new 前面带东西,会跳转不对

        myLogoutHandler.setDefaultTargetUrl("/login?code=1");

        http
                .authorizeRequests()
                .antMatchers(loginProcessingUrl, "/main/**", "/login*").permitAll()  //任何人都可以访问
                .anyRequest().authenticated() //其他所有资源都需要认证,登陆后访问

                // 这里使用withObjectPostProcessor来实现自定义的决策管理和资源角色管理器
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                        fsi.setAccessDecisionManager(myAccessDecisionManager);
                        fsi.setSecurityMetadataSource(mySecurityMetadataSource);
                        return fsi;
                    }
                })

                .and()
                .formLogin().loginPage("/login").loginProcessingUrl(loginProcessingUrl)
                .successHandler(myAuthenticationSuccessHandler()) // 登陆成功处理器
                .failureHandler(new MyAuthFailHandler("/login?code=-1")) // 登陆失败处理器
                .authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
                .permitAll()

                .and().exceptionHandling().accessDeniedPage("/login?code=-3")
                .and().logout().logoutUrl("/logout").logoutSuccessHandler(myLogoutHandler).permitAll()
                .and().csrf().disable()
        ;

        //UserDetails实现类Account那边要 重写equals()和hashCode()
        http.sessionManagement().maximumSessions(5).expiredUrl("/login?code=-5").sessionRegistry(sessionRegistry());
    }

    @Override
    public void configure(WebSecurity web) {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/assets/**", "/assets_mnt/**", "/api/**", "/dashboard/**");
        web.ignoring().antMatchers("/**/fav.ico", "/favicon.ico", "/robots.txt");
        web.ignoring().antMatchers("/**/captcha.jpg");
        web.ignoring().antMatchers("/ping", "/test", "/404_error", "/generic_error", "/error");
    }
}

其他SpringSecurity用到的自定义类

直接上代码,大部分都会在代码里做相关的注释,如有问题,请稍加修改再取用。

LoginController.java,从配置文件里可以看到我们把登录的url的定义为/login?code=xxx,根据xxx来判断一些登录的状态,如下:

@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(HttpSession session,
                          @RequestParam(value = "code", required = false) String codeStr) {
    ModelAndView model = new ModelAndView();
    String message = null;
    int code = 0;
    if (codeStr != null) {
        try {
            code = Integer.parseInt(codeStr);
        } catch (NumberFormatException ignored) {
        }
    }
    switch (code) {
        case -1:
            message = "登录失败";
            break;
        case -2:
            message = "验证码错误";
            break;
        case -3:
            message = "您无权访问指定资源,请登录到具有适当权限的用户";
            break;
        case -4:
            message = "管理员已重新设定权限,请重新登录";
            break;
        case -5:
            message = "请重新登录";//可能是session太多,超过了
            break;
        case -7:
            message = "账户已停用";
            break;
        case 1:
            message = "已成功注销";
            break;
    }
    if (message != null)
        model.addObject("message", message);
    model.setViewName("login");
    return model;
}

我做了一个简单的vue的login.jsp页面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%--
  ~ Copyright (c) 2022.
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  ~
  --%>


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>登录</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" type="text/css" href="${base}/assets/css/iview.css">
    <script type="text/javascript" src="assets/js/vue.min.js"></script>
    <script type="text/javascript" src="assets/js/iview.min.js"></script>
    <script type="text/javascript" src="assets/js/axios.min.js"></script>
    <script>
        window.Promise || document.write('<script src="assets/js/es6-promise.auto.min.js"><\/script><script type="text/javascript" src="assets/js/es6-shim.min.js"><\/script>');
    </script>

    <style type="text/css">
        #app, body, html {
            -webkit-font-smoothing: antialiased;
            width: 100%;
            height: 100%;
            position: relative;
        }

        .img-responsive {
            width: 100%;
            height: auto;
        }

        .base-app-style {
            /*min-width: 768px;*/
            position: relative;
            height: 100%;
            width: 100%;
            /*background: #3075ab;*/
            /*background: linear-gradient(to bottom,#343A40 0,#4B545C 100%);*/
            /*filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, StartColorStr='$fromColor', EndColorStr='$toColor');*/
            <%--background: url('${base}/assets/image/login_bg.jpg') no-repeat center center fixed;--%>
            -webkit-background-size: cover;
            -moz-background-size: cover;
            -o-background-size: cover;
            background-size: cover;
            /*background-size: 100%;*/
        }

        .footer {
            /*background-color: #000;*/
            position: fixed;
            left: 0;
            right: 0;
            bottom: 0;
            font-weight: 700;
            line-height: 1.4;
            padding: 10px;
            color: #7B7E81;
            text-align: center;
        }

        .gradient-text-one{
            /*background-image:-webkit-linear-gradient(bottom,#0B649E,#107596,#168C8C);*/
            /*-webkit-background-clip:text;*/
            /*-webkit-text-fill-color:transparent;*/
            /*text-shadow: 0px 1px 1px gray;*/

            color: #0B649E;
        }

        @media screen and (max-width: 769px){
            #app {
                padding: 0 15px;
            }

            .mobile-none {
                display: none;
            }

            #product {
                font-size: 34px;
                font-weight: 600;
                text-align: center;
            }
        }

        @media screen and (min-width: 769px){
            #product {
                top: calc(50% - 300px);
                /*left:50%;*/
                /*transform:translate(-50%,0);*/
                position: absolute;
                font-size: 44px;
                font-weight: 600;
                width: 100%;
                text-align: center;
            }

            #downloads {
                margin-top: 10px;
            }

            #downloads .downloads{
                display: flex;
                text-align: center;
                margin-top: 4px;
            }

            #downloads a {
                flex: 1;
            }
            #downloads a img {
                width:48px;
                height:48px;
                margin-bottom: 4px;
            }

            #login {
                width: 320px;
                left:50%;
                transform:translate(-50%,0);
                position: absolute;
                top: calc(50% - 150px);
            }

            .base-app-style {
                min-width: 768px;
            }

            .pc-none {
                display: none;
            }

        }

    </style>
</head>
<body class="base-app-style login-page">

<div id="app">
    <div style="padding: 40px;" id="product">
        <div class="gradient-text-one" style=""><%=Constants.SYSTEM_NAME%></div>
    </div>

    <div id="login">
        <card>
            <c:if test="${empty message}">
                <p slot="title">用户登录</p>
            </c:if>
            <i-form id="loginform" ref="loginform" action="${base}/j_spring_security_check" method="post"
                    @submit.native.prevent="handleSubmit()">
                <c:if test="${not empty message}">
                    <alert type="error">${message}</alert>
                </c:if>
                <div>
                    <%--                    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>--%>
                    <div>
                        <i-input placeholder="请输入用户名" size="large" style="margin-bottom:10px;" v-model="username"
                                 name="username" value='<c:out value="${sessionScope.LAST_USERNAME}"/>'>
                            <icon type="md-person" size="20" slot="prepend"/>
                        </i-input>
                    </div>
                    <div>
                        <i-input placeholder="请输入密码" type="password" size="large" style="margin-bottom:10px;"
                                 v-model="password" name="password"
                                 value='<c:out value="${sessionScope.LAST_PASSWORD}"/>'>
                            <icon type="md-lock" size="20" slot="prepend"/>
                        </i-input>
                    </div>

                    <row style="margin-bottom:10px;">
                        <i-col span="12">
                            <i-input name="captcha" v-model="captcha" size="large" placeholder="请输入验证码">
                                <icon type="md-checkmark" size="20" slot="prepend" />
                            </i-input>
                        </i-col>
                        <i-col span="12"><img :src="captchasrc" @click="refreshCaptcha" class="img-responsive" style="margin-left: 5px;"/></i-col>
                    </row>
                    <row>
                        <i-col span="12">
                            <Checkbox name='remember-me' size="large">保持登录</Checkbox>
                        </i-col>
                        <i-col span="12">
                            <%--                        <tooltip placement="top-start">--%>
                            <%--                            <div slot="content" style="text-align:left;">--%>
                            <%--                                <p>请联系XX</p>--%>
                            <%--                            </div>--%>
                            <%--                            <span style="color:#565D70;font-size: 16px;">忘记密码 <icon type="ios-help-circle" size="20" prepend/></span>--%>
                            <%--                        </tooltip>--%>
                        </i-col>
                    </row>


                    <i-button html-type="submit" :loading="loading" type="primary" long style="margin-top: 20px;"
                              size="large">
                        <span v-if="loading">登录中</span>
                        <span v-else>登录</span>
                    </i-button>

                </div>
            </i-form>

        </card>

    </div>

    <!-- /.login-box -->

    <div id="footer" class="footer mobile-none">
        Footer
    </div>
</div>


<script>
    new Vue({
        el: '#app',
        data: {
            username:'<c:out value="${sessionScope.LAST_USERNAME}"/>',
            password:'<c:out value="${sessionScope.LAST_PASSWORD}"/>',
            loading: false
        },
        created: function () {
            this.refreshCaptcha();
        },
        methods: {
            handleSubmit: function() {
                this.loading = true;
                document.getElementById('loginform').submit();
            },
            refreshCaptcha: function() {
                this.captchasrc='captcha.jpg?r='+Math.random();
                this.captcha = '';
            },
        }
    })
</script>

</body>
</html>

 

MySecurityMetadataSource.java

import cn.org.pcoic.manage.entity.Account;
import cn.org.pcoic.manage.entity.Module;
import cn.org.pcoic.manage.entity.Role;
import cn.org.pcoic.manage.service.intf.ISystemService;
import cn.org.pcoic.manage.util.Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date Jan 22, 2015 3:00:04 PM
 * @description 返回请求资源对应的角色
 */
@Service
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    private static final Log log = LogFactory.getLog(MySecurityMetadataSource.class);

    @Resource(name = "systemService")
    private ISystemService systemService;

    public static long ACCOUNT_LOAD_TIME_MIN = 0;//account里的loadTime如果小于这个时间,就要重新加载auth
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
    //这里存的是完整菜单,如果要输出菜单,还要根据当前登录的用户
    private static Map<Integer, List<Module>> menuModules = new HashMap<>();
    public static List<Module> menuModules2 = new ArrayList<>();
    public static String[] anyoneAccessUris = new String[]{"/login", "/logout", "/public"};

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return new ArrayList<>();
    }

    public static void reloadResourceDefineNextTime() {
        log.warn("set resourceMap null, will reload resource definition next time.");
        resourceMap = null;
    }

    public static Module getMenuModuleForUrl(String url) {
        if (url.equals("/")) {//FIXME 首页,暂时这么写死
            Module module = new Module();
            module.setName(Constants.HOME_MODULE_NAME);
            module.setUrl("/");
            module.setMenuLev(0);
            return module;
        }
        return menuModules2.stream().filter(module -> url.equals(module.getUrl())).findFirst().orElse(null);
//        for (Module module : menuModules2) {
//            if (url.equals(module.getUrl()))
//                return module;
//        }
//        return null;
    }

    public static List<Module> getMenuModule(int menuIndex, Account account) {
        if (account == null) {
            return getMenuModuleForCurrentUser(menuIndex);
        }
        return getMenuModule(menuIndex, account.getAuthorities());
    }

    public static List<Module> getMenuModule(int menuIndex) {
        return menuModules.get(menuIndex);
    }

    public static List<Module> getMenuModuleForCurrentUser(int menuIndex) {
        List<Module> modules = new ArrayList<>();
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null)
            return modules;
        return getMenuModule(menuIndex, authentication.getAuthorities());
    }

    public static List<Module> getMenuModule(int menuIndex, Collection<? extends GrantedAuthority> authorities) {
        List<Module> modules = new ArrayList<>();
        List<Module> modules1 = menuModules.get(menuIndex);
        if (modules1 != null) {
            for (Module module : modules1) {
                if (isUserCanAccessModule(authorities, module)) {
                    if (module.isShowInMenu())
                        modules.add(module);
                }
            }
        }

        return modules;
    }

    public static boolean isUserCanAccessModule(Collection<? extends GrantedAuthority> authorities, Module module) {
        List<Role> roles = module.getMroles();
        for (Role role : roles) {
            if (!role.isDisabled() && isUserHasRole(authorities, role))
                return true;
        }
        return false;
    }

    public static String isCurrentUserCanAccessUrlHtml(String url, String base, String name, String prefix) {
        if (!isCurrentUserCanAccessUrl(url)) {
            return "";
        }
        Module module = getMenuModuleForUrl(url);
        if (module == null) {
            return "";
        }
        return prefix + "<a href='" + base + url + "'>﹥" + (StringUtils.isEmpty(name) ? module.getName() : name) + "</a>";
    }

    public static boolean isCurrentUserCanAccessUrl(String url) {
        if (StringUtils.isEmpty(url)) {
            return false;
        }
        Account user = null;
        Object p = SecurityContextHolder.getContext().getAuthentication() == null ? null
                : SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (p == null || p instanceof String) {
        } else {
            user = (Account) p;
        }
        if (user == null) {
            return false;
        }
//        if (url.equals("/")) {//首页不加权限,直接能访问
//            return true;
//        }
        Module module = getMenuModuleForUrl(url);
        if (module == null) {
            return true;
        }
        return isUserCanAccessModule(user.getAuthorities(), module);
    }

    private static boolean isUserHasRole(Collection<? extends GrantedAuthority> authorities, Role role) {
        for (GrantedAuthority authority : authorities) {
            if (String.valueOf(role.getId()).equals(authority.getAuthority())) {
                return true;
            }
        }
        return false;
    }

    public static Collection<ConfigAttribute> getConfigAttributeForModuleUrl(String url) {
        if (resourceMap == null)
            return null;
        return resourceMap.get(url);
    }

    //加载所有资源与权限的关系
    private synchronized void loadResourceDefine() {
        //如果Role或者Module改变了,只要SecurityMetadataSource.reloadResourceDefineNextTime(); 那下次就用重新加载了
        if (resourceMap != null)
            return;
        resourceMap = new HashMap<>();
        menuModules.clear();
        menuModules2.clear();
        List<Module> resources = systemService.listModules();
        for (Module module : resources) {
            if (module.isDisabled()) {
                continue;
            }
            if (module.getMenuLev() == 0) {
                Constants.HOME_MODULE_NAME = module.getName();
            }
            menuModules2.add(module);
            int menuIndex = (module.getMenuLev() - module.getMenuLev() % 100) / 100;
            List<Module> modules = menuModules.get(menuIndex);
            if (modules == null) {
                modules = new ArrayList<>();
            }
            modules.add(module);
            menuModules.put(menuIndex, modules);
            //获得拥有此权限的所有角色
            List<Role> roles = module.getMroles();

            Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
            //以角色ID名封装为Spring的security Object,此后交给spring进行管理
            for (Role role : roles) {
                if (role.isDisabled()) {
                    continue;
                }
//                System.out.println("requestUrl对应的角色 " + role.getName());
                ConfigAttribute configAttribute = new SecurityConfig(String.valueOf(role.getId()));
                configAttributes.add(configAttribute);
            }

            //如果还没有指定可以访问的角色,那么硬编码成普通用户可以看
            if (configAttributes.size() == 0) {
                configAttributes.add(new SecurityConfig("1"));
            }
            resourceMap.put(module.getUrl(), configAttributes);
        }

//        Set<Map.Entry<String, Collection<ConfigAttribute>>> resourceSet =
//                resourceMap.entrySet();
//        System.out.println(resourceSet.isEmpty());
//        for
//                (Map.Entry<String, Collection<ConfigAttribute>> entry : resourceSet) {
//            System.out.println(entry.getKey() + "," + entry.getValue());
//        }
//        Iterator<Map.Entry<String, Collection<ConfigAttribute>>> iterator =
//                resourceSet.iterator();

    }

    /**
     * 返回所请求资源对应的角色
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //截取问号前面的URL进行判断,http://localhost:8080/sys/userlist?page=0,这样的URL数据库中没有,
        //故访问不到,数据库中只存储了不带问号的
        String originalUrl = ((FilterInvocation) object).getRequestUrl();
        String requestUrl = parserUrl(originalUrl);
//        log.info("originalUrl:" + originalUrl + ", parsed: " + requestUrl);
        //这里强制让缓存清空,调试用的!!!
//        resourceMap = null;
        if (resourceMap == null) {
            loadResourceDefine();
        }

        boolean isCanAccess = false;
        for (String accessUris : anyoneAccessUris) {
            if (accessUris.equals(requestUrl)) {
                isCanAccess = true;
                break;
            }
        }
        if (requestUrl.startsWith("/public/")) {
            isCanAccess = true;
        }

        if (isCanAccess) {
            return null;
        }

        Collection<ConfigAttribute> configAttributes = resourceMap.get(requestUrl);
        if (configAttributes == null || configAttributes.size() == 0) {
            if (requestUrl.contains("/WEB-INF/")) {
                return null;
            }
//            if (requestUrl.equals("/")) {//首页不要加权限了,直接能访问
//                return null;
//            }
            log.warn("no module for request url: " + requestUrl);
            configAttributes = new ArrayList<>();
            configAttributes.add(new SecurityConfig("0"));//加一个不存在的role,让这个无法访问,必须要手动到模块那边去添加
//            if (!Constants.PRODUCT_ENV) {//开发环境就创建这个URL
//                systemService.createModuleForUrl(requestUrl);
//            }
        }
        return configAttributes;
    }

    /**
     * Url可能是/sys/employeesMap/list 我们只要前面2个就可以了
     *
     * @param requestUrl
     * @return
     */
    private String parserUrl(String requestUrl) {
        if (StringUtils.isEmpty(requestUrl) || requestUrl.equals("/"))
            return "/";
        String[] parts = requestUrl.split("/");
        if (parts.length < 4) {
            return removeParameterFromUrl(requestUrl);
        } else {
            return removeParameterFromUrl(parts[0] + "/" + parts[1] + "/" + parts[2]);
        }

    }

    /**
     * 去掉url后面的?#|什么的参数
     *
     * @param requestUrl
     * @return
     */
    private String removeParameterFromUrl(String requestUrl) {
        Pattern p = Pattern.compile("([\\.//_-]*\\w)*");
        Matcher m = p.matcher(requestUrl);
        if (m.find()) {
            return m.group();
        }
        return requestUrl;
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

}

MyAccessDecisionManager.java

import cn.org.pcoic.manage.entity.Account;
import cn.org.pcoic.manage.service.intf.ISystemService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collection;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date Jan 22, 2015 2:55:08 PM
 * @description 匹配用户所属的角色和请求资源所属的角色,如果能匹配的上,则访问请求的资源,
 * 否则没有权限访问,会跳转到login.jsp页面
 * spring给你做决定
 * spring把一些东西交给你判断
 * 你觉得根据这些东西决定是否允许用户操作
 */
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {

    private static final Log log = LogFactory.getLog(MyAccessDecisionManager.class);

    @Resource(name = "systemService")
    private ISystemService systemService;

    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        if (configAttributes == null) {     //现在数据库中没有的URL,这里都会直接显示页面,目前还没处理
            throw new AccessDeniedException("访问路径不存在!");
        }
        if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof Account
                && ((Account) authentication.getPrincipal()).loadTime < MySecurityMetadataSource.ACCOUNT_LOAD_TIME_MIN) {
            Account account = systemService.getAccountByNo(authentication.getName());
            Authentication newAuth = new UsernamePasswordAuthenticationToken(account, account.getPassword(), account.getAuthorities());
            authentication = newAuth;
            log.warn("reload new auth for: " + account.getNo() + ", time: " + account.loadTime);
            SecurityContextHolder.getContext().setAuthentication(newAuth);
        }
        // 所请求的资源对应的角色(一个资源对多个角色)
        for (ConfigAttribute configAttribute : configAttributes) {
            // 是role-id
            String neededRoleId = configAttribute.getAttribute();
            if("0".equals(neededRoleId) && !isAnonymous(authentication)) {//MySecurityMetadataSource的277行,首页模块不存在,就加了个roleID是0,登录用户能访问
                return;
            }
            // 用户所拥有的权限authentication
            for (GrantedAuthority ga : authentication.getAuthorities()) {
//                System.out.println("用户所拥有的权: " + ga.getAuthority());
                if (neededRoleId.equals(ga.getAuthority())) {    //若请求的权限在用户拥有的权限内,则请求成功
                    return;
                }
            }
        }
        // 没有权限会跳转到login.jsp页面
        throw new AccessDeniedException("没有权限访问");
    }

    //判断是否是匿名访问
    private boolean isAnonymous(Authentication authentication) {
        if (authentication.getAuthorities() == null || authentication.getAuthorities().size() == 0) {
            return true;
        }
        if (authentication.getAuthorities().size() == 1) {
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (ga.getAuthority().equals("ROLE_ANONYMOUS")) {
                    return true;
                }
            }
        }
        return false;
    }    
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

MyAuthenticationProvider.java

import cn.org.pcoic.manage.entity.Account;
import cn.org.pcoic.manage.service.intf.ISystemService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date Jul 8, 2015 2:28:55 PM
 * @description
 */
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

    private static final Log log = LogFactory.getLog(MyAuthenticationProvider.class);

    @Resource(name = "systemService")
    private ISystemService systemService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String no = authentication.getName();
        String password = authentication.getCredentials().toString();

        Object details = authentication.getDetails();
        String ipAddress = null;
        if (details instanceof MyWebAuthenticationDetails)
            ipAddress = ((MyWebAuthenticationDetails) details).getRemoteAddress();

        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String passwordNotMatch = "用户名或密码错误";
        Account account = systemService.getAccountByNo(no); // 数据库中查询此用户
        if (account == null) {
//            throw new UsernameNotFoundException(Constants.AUTH_WEB);
            throw new BadCredentialsException(passwordNotMatch);
        }
        if (!account.isEnabled() || !account.isAccountNonLocked()) {
            throw new LockedException("账户已停用");
        }

        boolean matches = passwordEncoder.matches(password, account.getPassword());

        if (!matches) {
            throw new BadCredentialsException(passwordNotMatch);
        }

        log.warn(WebSecurityConfig.SECURITY_KEY + " 密码登录,姓名:" + account.getName()
                + ",帐号:" + account.getNo() + ",ID:" + account.getId() + ",IP:" + ipAddress);
        systemService.updateAccountLastLogin(account.getId(), ipAddress);
        return new UsernamePasswordAuthenticationToken(account, password, account.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

MyAuthenticationSuccessHandler.java

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

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

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2019-07-08 19:49
 * @description
 */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private static final Log log = LogFactory.getLog(MyAuthenticationSuccessHandler.class);

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response, Authentication authentication) throws IOException {
        handle(request, response, authentication);
        clearAuthenticationAttributes(request);
    }

    protected void handle(HttpServletRequest request,
                          HttpServletResponse response, Authentication authentication) throws IOException {
        String targetUrl = determineTargetUrl(authentication);
        if (response.isCommitted()) {
            log.debug("Response has already been committed. Unable to redirect to " + targetUrl);
            return;
        }
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    protected String determineTargetUrl(Authentication authentication) {
        return "/";
//        boolean isAdmin = false;
//        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//        for (GrantedAuthority grantedAuthority : authorities) {
//            if (grantedAuthority.getAuthority().equals("1")) {
//                isAdmin = true;
//                break;
//            }
//        }
//
//        if (isAdmin) {
//            return "/sys/accounts";
//        } else {
//            return "/";
////            throw new IllegalStateException();
//        }
    }

    protected void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return;
        }
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }

    protected RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }
}

MyAuthFailHandler.java

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2015-9-14 09:57
 * @description 如果登录发生错误,记住用户名 参考:
 * http://stackoverflow.com/questions/15052860/spring-security-authentication-get-username-without-spring-security-last-username
 */
public class MyAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
    public static final String LAST_USERNAME_KEY = "LAST_USERNAME";
    public static final String LAST_PASSWORD_KEY = "LAST_PASSWORD";
    public static final String LAST_AUTH_FAIL_MSG = "LAST_AUTH_FAIL_MSG";

    public MyAuthFailHandler(String defaultFailureUrl){
        super(defaultFailureUrl);
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {
        super.onAuthenticationFailure(request, response, exception);

        String lastUserName = request.getParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY);

        HttpSession session = request.getSession(false);
        if (session != null || isAllowSessionCreation()) {
            session.setAttribute(LAST_USERNAME_KEY, lastUserName);
            session.setAttribute(LAST_PASSWORD_KEY, "");
            if (exception != null && exception.getMessage() != null) {
                session.setAttribute(LAST_AUTH_FAIL_MSG, exception.getMessage());
            }
        }
    }

}

MyUserDetailService.java

import cn.org.pcoic.manage.entity.Account;
import cn.org.pcoic.manage.service.intf.ISystemService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import javax.annotation.Resource;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2021-7-7 19:34
 * @description
 */
@Configuration("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {

    private static final Log log = LogFactory.getLog(MyUserDetailService.class);

    @Resource(name = "systemService")
    private ISystemService systemService;

    // 用户一登陆就会调用此方法验证,登陆界面也是spring管理的,不需要自己来写了,login.jsp里用到spring
    @Override
    public UserDetails loadUserByUsername(String no) throws UsernameNotFoundException {
        Account account = systemService.getAccountByNo(no); // 数据库中查询此用户
        if (account == null) { // 判断此用户是否存在
            throw new UsernameNotFoundException(no);
        }
        if (!account.isEnabled() || !account.isAccountNonLocked()) {
            throw new LockedException("账户已停用");
        }
        log.warn(WebSecurityConfig.SECURITY_KEY + " Cookie登录,姓名:" + account.getName() + ",帐号:" + account.getNo() + ",ID:" + account.getId());
        systemService.updateAccountLastLogin(account.getId(), null);
        return account;
    }
}

MyWebAuthenticationDetails.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.Serializable;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2018-3-23 09:54
 * @description
 */
public class MyWebAuthenticationDetails implements Serializable {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final String remoteAddress;
    private final String sessionId;

    public MyWebAuthenticationDetails(HttpServletRequest request) {
        this.remoteAddress = Utils.getIpAddr(request);
        HttpSession session = request.getSession(false);
        this.sessionId = session != null ? session.getId() : null;
    }

    public boolean equals(Object obj) {
        if (obj instanceof WebAuthenticationDetails) {
            WebAuthenticationDetails rhs = (WebAuthenticationDetails)obj;
            if (this.remoteAddress == null && rhs.getRemoteAddress() != null) {
                return false;
            } else if (this.remoteAddress != null && rhs.getRemoteAddress() == null) {
                return false;
            } else if (this.remoteAddress != null && !this.remoteAddress.equals(rhs.getRemoteAddress())) {
                return false;
            } else if (this.sessionId == null && rhs.getSessionId() != null) {
                return false;
            } else if (this.sessionId != null && rhs.getSessionId() == null) {
                return false;
            } else {
                return this.sessionId == null || this.sessionId.equals(rhs.getSessionId());
            }
        } else {
            return false;
        }
    }

    public String getRemoteAddress() {
        return this.remoteAddress;
    }

    public String getSessionId() {
        return this.sessionId;
    }

    public int hashCode() {
        int code = 7654;
        if (this.remoteAddress != null) {
            code *= this.remoteAddress.hashCode() % 7;
        }

        if (this.sessionId != null) {
            code *= this.sessionId.hashCode() % 7;
        }

        return code;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString()).append(": ");
        sb.append("RemoteIpAddress: ").append(this.getRemoteAddress()).append("; ");
        sb.append("SessionId: ").append(this.getSessionId());
        return sb.toString();
    }
}

MyWebAuthenticationDetailsSource.java

import org.springframework.security.authentication.AuthenticationDetailsSource;

import javax.servlet.http.HttpServletRequest;

public class MyWebAuthenticationDetailsSource
        implements AuthenticationDetailsSource<HttpServletRequest, MyWebAuthenticationDetails> {

    @Override
    public MyWebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new MyWebAuthenticationDetails(context);
    }
}

 

 

admin

这个人很懒,什么都没留下

文章评论

您需要 登录 之后才可以评论