zoukankan      html  css  js  c++  java
  • Spring Boot整合Spring Security自定义登录实战

    本文主要介绍在Spring Boot中整合Spring Security,对于Spring Boot配置及使用不做过多介绍,还不了解的同学可以先学习下Spring Boot。

    本demo所用Spring Boot版本为2.1.4.RELEASE。

    1、pom.xml中增加依赖

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

    2、Spring Security配置类

    package com.inspur.webframe.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    
    import com.inspur.webframe.security.UserDetailsServiceImpl;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Bean
        @Override
        protected UserDetailsService userDetailsService() {
            return new UserDetailsServiceImpl();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
            .authorizeRequests().anyRequest().authenticated()
            .and()
                .csrf().disable() //禁用csrf
                .headers().frameOptions().disable() //禁用frame options
            .and()
                .formLogin().loginPage("/demo/login").loginProcessingUrl("/j_spring_security_check").failureUrl("/demo/login?error=true").defaultSuccessUrl("/demo/main").permitAll()
            .and()
                .logout().logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/demo/login").permitAll();
        }
    }

     userDetailsService返回自己实现的UserDetailsService,见下面UserDetailsServiceImpl类。

    configure方法中配置了如下内容:

      登录页面url:/demo/login

      登录处理url:/j_spring_security_check,对应登录页面中登录操作url

      登录失败url:/demo/login?error=true

      登录成功url:/demo/main

      注销url:/j_spring_security_logout,对应欢迎页面中注销操作url

      注销成功跳转url:/demo/login,调到登录页面

    3、用户类

    该类与数据库的用户表对应

    package com.inspur.webframe.security;
    
    import java.io.Serializable;
    
    import com.inspur.common.entity.BaseEntity;
    
    public class User extends BaseEntity implements Serializable {
        private static final long serialVersionUID = 1L;
        /**
         * 用户id
         */
        private String userid;
        
        /**
         * 用户密码
         */
        private String password;
        
        /**
         * 用户名
         */
        private String username;
        
        /**
         * 是否被锁定 1:是 0:否
         */
        private Integer isLocked;
        
        public Integer getIsLocked() {
            return isLocked;
        }
        public void setIsLocked(Integer isLocked) {
            this.isLocked = isLocked;
        }
        public String getUserid() {
            return userid;
        }
        public void setUserid(String userid) {
            this.userid = userid;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        
        @Override
        public String toString() {
            return "User [userid=" + userid + ", password=" + password + ", username=" + username + "]";
        }
    }

    BaseEntity是一个基类,有id、创建时间、修改时间等基础信息,作为demo可以忽略

    4、自定义UserDetails

    该类需要实现org.springframework.security.core.userdetails.UserDetails接口,作为用户信息;该类关联用户类

    package com.inspur.webframe.security;
    
    import java.util.Collection;
    
    import org.apache.commons.codec.binary.Base64;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import com.inspur.common.util.EncoderUtil;
    
    public class SecurityUser implements UserDetails {
        private static final long serialVersionUID = 4118167338060103803L;
        private User systemUser = null;
        private Collection<? extends GrantedAuthority> authorities = null;
        
        public SecurityUser(User systemUser, Collection<? extends GrantedAuthority> authorities) {
            this.systemUser = systemUser;
            this.authorities = authorities;
        }
        
        public User getSystemUser() {
            return systemUser;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        @Override
        public String getPassword() {
         //{MD5}e10adc3949ba59abbe56e057f20f883e,123456
       return systemUser.getPassword(); } @Override public String getUsername() { return systemUser.getUserid(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return !(systemUser.getIsLocked() != null && systemUser.getIsLocked() == 1); } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

     spring security 有多种验证密码算法,这里使用的MD5算法,格式为:{MD5}e10adc3949ba59abbe56e057f20f883e;如果数据保存的密码格式不是这种格式,可以在getPassword()方法中转换成标准格式。

    5、自定义UserDetailsService

    该类需要实现org.springframework.security.core.userdetails.UserDetailsService接口,用于用户的登录认证

    package com.inspur.webframe.security;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    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 com.inspur.common.dao.BaseDao;
    
    public class UserDetailsServiceImpl implements UserDetailsService {
        protected static Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
        
        @Autowired
        private BaseDao baseDao;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            logger.info(username);
            User user = baseDao.selectForObject(User.class, "userid=?", username);
            logger.info("user={}", user);
            
            if (user != null) {
                //权限,应从数据取这里写死
                List<GrantedAuthority> authorities= new ArrayList<GrantedAuthority>();
                authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
                authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
                SecurityUser u = new SecurityUser(user, authorities);
                
                logger.info(u.getPassword());
                return u;
            }
            throw new UsernameNotFoundException("用户(" + username + ")不存在");
        }
    }

    baseDao是我实现的操作数据库的工具类,类似spring的jdbcTemplate;不是重点,具体实现细节就不贴出来了,看代码也能看出意思

    6、访问url的controller

    package com.inspur.demo.web.controller;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import com.inspur.common.web.controller.BaseController;
    
    @Controller
    @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
    public class DemoController extends BaseController {
        
        @RequestMapping(value={"/demo/login", "/"})
        public String login() {
            return "/demo/login";
        }
       
        @RequestMapping(value={"/demo/main"})
        public String main(Authentication authentication) {
            logger.info("authentication.getPrincipal()={}", authentication.getPrincipal());
            return "/demo/main";
        }
        
    }

    7、thymeleaf

    thymeleaf是spring推荐使用的模板引擎,可以优雅的来画页面。

    • 引入依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    • 配置application.yml(或application.properties)
    spring:
      resources:
        static-locations: classpath:/static/
      thymeleaf:
        encoding: utf-8
        cache: false

    8、编写页面login.html

    页面位置为/src/main/resources/templates/demo/login.html

    <!DOCTYPE HTML>
    <html>
      <head>
        <title>My JSP 'login.jsp' starting page</title>
        
        <meta http-equiv="pragma" content="no-cache">
        <meta http-equiv="cache-control" content="no-cache">
        <meta http-equiv="expires" content="0">    
    
      </head>
      
      <body>
        <form th:action="@{/j_spring_security_check}" method="post">
        <input type="hidden"  name ="${_csrf.parameterName}"  value ="${_csrf.token}" /> 
          <table>
             <tr>
                <td> 用户名:</td>
                <td><input type="text" name="username"/></td>
             </tr>
             <tr>
                <td> 密码:</td>
                <td><input type="password" name="password"/></td>
             </tr>
             <tr>
                <td>
                    <span style="color: red;" th:if="${param.error != null && session.SPRING_SECURITY_LAST_EXCEPTION != null }" th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></span>
                </td>
             </tr>
             <tr>
                <td colspan="2" align="center">
                    <input type="submit" value=" 登录 "/>
                    <input type="reset" value=" 重置 "/>
                </td>
             </tr>
          </table>
       </form>
      </body>
    </html>

    /j_spring_security_check对应上面SecurityConfig配置的登录路径;session.SPRING_SECURITY_LAST_EXCEPTION.message表示登录错误的信息。

    9、编写页面main.html

    页面位置为/src/main/resources/templates/demo/main.html

    <!DOCTYPE HTML>
    <html>
      <head>
        <title>My JSP 'main.jsp' starting page</title>
        
        <meta http-equiv="pragma" content="no-cache">
        <meta http-equiv="cache-control" content="no-cache">
        <meta http-equiv="expires" content="0">    
    
      </head>
      
      <body>
      欢迎! <a th:href="@{/j_spring_security_logout}">退出</a>
      </body>
    </html>

    /j_spring_security_check对应上面SecurityConfig配置的注销路径

    10、测试

    访问登录页面http://localhost:8080/webframe/demo/login,我的server.servlet.context-path配置为/webframe

      登录失败:

      登录成功:

    11、扩展功能-锁定用户

    简单实现:用户表中需要有is_locked(是否锁定)、login_fail_times(连续登录失败次数)这两个字段;连续登录失败次数超过一定值就锁定用户。

    增加listener监听登录事件。

    package com.inspur.webframe.security;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationListener;
    import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
    import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
    import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
    import org.springframework.stereotype.Component;
    
    import com.inspur.common.dao.BaseDao;
    
    @Component
    public class LoginListener implements ApplicationListener<AbstractAuthenticationEvent> {
        private static Logger logger = LoggerFactory.getLogger(LoginListener.class);
        private static int MAX_FAIL_TIMES = 3;
        @Autowired
        private BaseDao baseDao;
        
        @Override
        public void onApplicationEvent(AbstractAuthenticationEvent event) {
            logger.info(event.getClass().toString());
            String userId = event.getAuthentication().getName();
            if (event instanceof AuthenticationSuccessEvent) {
                baseDao.update("update a_hr_userinfo set login_fail_times=0 where userid=? and login_fail_times>0", userId);
            } else if (event instanceof AuthenticationFailureBadCredentialsEvent) {
                baseDao.update("update a_hr_userinfo set login_fail_times=login_fail_times+1 where userid=?", userId);
                baseDao.update("update a_hr_userinfo set is_locked=1 where userid=? and login_fail_times>=?", userId, MAX_FAIL_TIMES);
            }
        }
    }

    登录成功login_fail_times清0,登录失败login_fail_times加1,到达3就锁定用户;这边也用到了baseDao,具体意思看代码也能明白。

  • 相关阅读:
    destoon手机端mobileurl函数增加城市分类参数
    jCarousel,jQuery下的滚动切换传送插件
    jQuery plugin : bgStretcher 背景图片切换效果插件
    jquery图片切换插件jquery.cycle.js参数详解
    destoon 后台管理左侧新增菜单项
    destoon 列表页面增加手动选择排序方式
    jQuery 淡入淡出有png图的时候 ie8下有黑色边框
    java中Array和ArrayList区别
    趣味理解ADO.NET对象模型
    两个datatable的比较
  • 原文地址:https://www.cnblogs.com/wuyongyin/p/11728746.html
Copyright © 2011-2022 走看看