zoukankan      html  css  js  c++  java
  • Spring-security整理

    出于某些原因,需要学习一下spring的安全框架。(研究半天,如果单单说用户认证和授权这块儿,我感觉还是shiro好用。)

    spring security介绍可以参考一下以下文档:

    (满满的羡慕啊)我这里就不扯了。

    http://www.tianshouzhi.com/api/tutorials/spring_security_4/252

    一、先贴代码

    下边是我的项目结构

    当然我采用的是springboot + thymeleaf + mybatis + mysql

    1.用到的pom.xml

     1         <dependency>
     2             <groupId>org.springframework.boot</groupId>
     3             <artifactId>spring-boot-starter-web</artifactId>
     4         </dependency>
     5 
     6         <dependency>
     7             <groupId>org.springframework.boot</groupId>
     8             <artifactId>spring-boot-starter-test</artifactId>
     9             <scope>test</scope>
    10         </dependency>
    11 
    12         <dependency>
    13             <groupId>org.mybatis.spring.boot</groupId>
    14             <artifactId>mybatis-spring-boot-starter</artifactId>
    15             <version>2.0.1</version>
    16         </dependency>
    17 
    18 <!--     mysql    -->
    19         <dependency>
    20             <groupId>mysql</groupId>
    21             <artifactId>mysql-connector-java</artifactId>
    22             <version>5.1.32</version>
    23         </dependency>
    24 
    25 <!--     lombok   -->
    26         <dependency>
    27             <groupId>org.projectlombok</groupId>
    28             <artifactId>lombok</artifactId>
    29             <version>1.18.8</version>
    30             <scope>provided</scope>
    31         </dependency>
    32 
    33 <!--    security+thymeleaf    -->
    34         <dependency>
    35             <groupId>org.springframework.boot</groupId>
    36             <artifactId>spring-boot-starter-security</artifactId>
    37             <version>2.1.5.RELEASE</version>
    38         </dependency>
    39 
    40         <dependency>
    41             <groupId>org.springframework.boot</groupId>
    42             <artifactId>spring-boot-starter-thymeleaf</artifactId>
    43 <!--            <version>2.1.3.RELEASE</version>-->
    44         </dependency>
    45         <dependency>
    46             <groupId>org.thymeleaf.extras</groupId>
    47             <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    48             <version>3.0.2.RELEASE</version>
    49         </dependency>
    pom.xml

    2.核心配置类

     1 import org.springframework.context.annotation.Bean;
     2 import org.springframework.context.annotation.Configuration;
     3 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
     4 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     5 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
     6 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
     7 import org.springframework.security.core.userdetails.UserDetailsService;
     8 import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
     9 
    10 @Configuration
    11 @EnableWebSecurity
    12 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    13     @Bean
    14     UserDetailsService customUserService(){ //注册UserDetailsService 的bean
    15         return new UserDetailsServiceImpl();
    16     }
    17 
    18     @Override
    19     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    20         auth.userDetailsService(customUserService()).passwordEncoder(new MessageDigestPasswordEncoder("MD5")); //user Details Service验证
    21     }
    22     @Override
    23     protected void configure(HttpSecurity http) throws Exception {
    24         http.authorizeRequests()
    25                 .anyRequest().authenticated() //authenticated任何请求,登录后可以访问
    26                 .and()
    27                 .csrf().disable()
    28                 .formLogin()
    29                 .loginPage("/user/login")//
    30                 .defaultSuccessUrl("/user/index")
    31                 .failureUrl("/user/login?error=true")
    32                 .permitAll() //登录页面用户任意访问
    33                 .and()
    34                 .logout().permitAll(); //注销行为任意访问
    35     }
    36 }
    WebSecurityConfig.java

    3.UserDetailsServiceImpl

     1 import com.qx.demo.entity.RolePo;
     2 import com.qx.demo.entity.UserPo;
     3 import com.qx.demo.service.UserService;
     4 import org.springframework.beans.factory.annotation.Autowired;
     5 import org.springframework.security.core.authority.SimpleGrantedAuthority;
     6 import org.springframework.security.core.userdetails.User;
     7 import org.springframework.security.core.userdetails.UserDetails;
     8 import org.springframework.security.core.userdetails.UserDetailsService;
     9 import org.springframework.security.core.userdetails.UsernameNotFoundException;
    10 
    11 import java.util.HashSet;
    12 import java.util.Set;
    13 
    14 public class UserDetailsServiceImpl implements UserDetailsService {
    15     @Autowired
    16     UserService service;
    17     @Override
    18     public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    19         UserPo user = service.getUserById(s);
    20         if (user==null){
    21             throw new UsernameNotFoundException("用户名不存在");
    22         }
    23         Set<SimpleGrantedAuthority> authorities = new HashSet<>();
    24         for (RolePo item:user.getRoles()){
    25             authorities.add(new SimpleGrantedAuthority(item.getRName()));
    26             System.out.println(item.getRName());
    27         }
    28         return new User(user.getUsername(),user.getPassword(),authorities);
    29     }
    30 }
    UserDetailsServiceImpl.java

    4.控制层Controller

     1 import com.qx.demo.service.UserService;
     2 import org.springframework.beans.factory.annotation.Autowired;
     3 import org.springframework.security.core.Authentication;
     4 import org.springframework.security.core.userdetails.User;
     5 import org.springframework.stereotype.Controller;
     6 import org.springframework.web.bind.annotation.RequestMapping;
     7 import org.springframework.web.servlet.ModelAndView;
     8 
     9 import javax.servlet.http.HttpServletRequest;
    10 
    11 @Controller
    12 @RequestMapping("user")
    13 public class UserController {
    14     @Autowired
    15     private UserService service;
    16     @RequestMapping("login")
    17     public ModelAndView toLogin(HttpServletRequest request){
    18         ModelAndView mv= new ModelAndView("login");
    19         String error = request.getParameter("error");
    20         if (error!=null && error.equals("true")){
    21             System.out.println("登录失败");
    22         }
    23         return mv;
    24     }
    25     @RequestMapping("index")
    26     public String getUserById( Authentication authentication){
    27         User principal = (User)authentication.getPrincipal();
    28         if (authentication!=null)
    29             System.out.println(authentication.getCredentials()+",
    "+authentication.getDetails()+",
    "+authentication.getPrincipal()+",
    "+authentication.getName());
    30         return "index";
    31     }
    32 }
    UserController.java

    5.实体类

     1 import lombok.Data;
     2 import lombok.ToString;
     3 
     4 import java.util.List;
     5 
     6 @Data
     7 @ToString
     8 public class UserPo {
     9     private int uid;
    10     private String username;
    11     private String password;
    12     private List<RolePo> roles;
    13 }
    UserPo.java
     1 import lombok.Data;
     2 import lombok.ToString;
     3 
     4 import java.util.List;
     5 
     6 @Data
     7 @ToString
     8 public class RolePo {
     9     private int rid;
    10     private String rName;
    11     private List<PermissionPo> pers;
    12 }
    RolePo.java
     1 import lombok.Data;
     2 import lombok.ToString;
     3 
     4 @Data
     5 @ToString
     6 public class PermissionPo {
     7     private int pid;
     8     private String pName;
     9     private String pPer;
    10 }
    PermissionPo

    6.Dao层

    1 import com.qx.demo.entity.UserPo;
    2 
    3 public interface UserMapper {
    4     UserPo getUserByUsername(String username);
    5 }
    UserMapper.java
     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     3         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     4 <mapper namespace="com.qx.demo.dao.UserMapper">
     5 
     6     <resultMap id="getRoleAndPer" type="com.qx.demo.entity.UserPo">
     7         <id column="uid" property="uid"></id>
     8         <result column="username" property="username"></result>
     9         <result column="password" property="password"></result>
    10         <collection property="roles" ofType="com.qx.demo.entity.RolePo">
    11             <id column="rid" property="rid"></id>
    12             <result column="r_name" property="rName"></result>
    13             <collection property="pers" ofType="com.qx.demo.entity.PermissionPo">
    14                 <id column="pid" property="pid"></id>
    15                 <result column="p_name" property="pName"></result>
    16                 <result column="p_per" property="pPer"></result>
    17             </collection>
    18         </collection>
    19     </resultMap>
    20     <select id="getUserByUsername"  parameterType="String" resultMap="getRoleAndPer">
    21         SELECT * FROM qx_user qu
    22             INNER JOIN `qx_user_role` qur ON qu.`uid`=qur.`u_id`
    23             INNER JOIN qx_role qr ON qur.`r_id` = qr.`rid`
    24             INNER JOIN qx_role_per qrp ON qr.`rid`=qrp.`pid`
    25             INNER JOIN qx_permission qp ON qrp.`pid`=qp.`pid`
    26             WHERE username=#{_parameter}
    27     </select>
    28 </mapper>
    Mapper.xml

    7.service层

    1 import com.qx.demo.entity.UserPo;
    2 
    3 public interface UserService {
    4     UserPo getUserById(String id);
    5 }
    UserService.java
     1 import com.qx.demo.dao.UserMapper;
     2 import com.qx.demo.entity.UserPo;
     3 import com.qx.demo.service.UserService;
     4 import org.springframework.stereotype.Service;
     5 
     6 import javax.annotation.Resource;
     7 
     8 @Service
     9 public class UserServiceImpl implements UserService {
    10     @Resource
    11     private UserMapper mapper;
    12     @Override
    13     public UserPo getUserById(String  username) {
    14         return mapper.getUserByUsername(username);
    15     }
    16 }
    UserServiceImpl.java

    8.application.properties

     1 mybatis.mapper-locations=/mapper/*.xml
     2 mybatis.type-aliases-package=com.qx.demo.entity
     3 
     4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
     5 spring.datasource.url=jdbc:mysql:///qx
     6 spring.datasource.username=root
     7 spring.datasource.data-password=root
     8 
     9 spring.thymeleaf.mode=HTML5
    10 spring.thymeleaf.cache=false
    11 
    12 logging.level.com.qx.demo.dao=debug
    application.properties

    二、再来看看spring-security的大概的执行流程

    1.从上边图不难看出,spring-security的核心应该就是ProviderManager,但是ProviderManager并不直接执行认证和授权等相关操作,而是委托给AuthenticationProvider去完成相关操作,而对于每一个ProviderManager都是可以有多个AuthenticationProvider的。

    2.AuthenticationProvider首先会找到UserDetailsService(有我们自己定义的)在数据库中查找用户信息。如果没有找到将会返回一个空的UserDetails,由于UserDetails是一个接口,我是在实体类中实现了该接口(那么这样我们就可以返回一个空壳user对象了)。

       当然,如果找到了我们可以直接授权并将UserDetailsService返回。

    3.AuthenticationProvider就收到一个非空的UserDetailsService,接下来就该进行密码比对了。这时AuthenticationProvider将会调用PasswordEncoder进行密码比对。

    4.无论成功与否,spring-security都会在对应的过滤器上找到响应的响应方式,并响应给客户。

    Spring-security和shiro一样,都是基于过滤器,所以我们免不了去配置一些过滤器:

      

    当然,在前边的代码中已经体现过,该配置应该是在spring-security的核心配置类中进行配置(如果是xml也是一样的,大同小异嘛)。

    上图是基于前后端分离,给前端返回一个json格式响应体的,比如登录成功时:

    如图所示我们只需要实现AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法即可。这个方法体完全就是一个类似与Servlet的方法体,有一定javaee基础的应该都没啥问题的。同样的道理如果登陆失败我们可以实现AuthenticationFailureHandler等等。

    嗯,可能我忘了记录一个至关重要的玩意了,没错就是他:

    你没看错,这就是一个基于用户名和密码的filter,spring-security拼什么这么智能,其实很简单就是一系列过滤器的使用。在源码中我们不难看出spring-security是设置了默认的登录地址的  /login,并且请求只允许POST请求。同时他也是spring-security默认登录过滤器。由于Java的开放原则的原因,你要是看他不爽你也可以自定义一个过滤器,你只需要继承一下AbstractAuthenticationProcessingFilter,重写其中的一些方法就好了。一般情况下我觉得默认的就可以了。

    同样的与shiro雷同UsernamePasswordAuthenticationToken就是一个主体,当然保存的是客户端输入的登录信息。他最终是与UserDetailsService(在数据库中查到的信息)相对比得到登录结果。

    对于用户信息来说我想密码应该是最重要的了,那么我们现在可以看看PasswordEncoder了

    以上是security5中所有的PasswordEncoder了,当然,上边截图中有一个是我重写的PasswordEncoder(MyPasswordEncoder是我重写的)。这里我就不解释这些密码比对器都是什么作用了,如果有不了解的大家可以自己下去查询一下资料。我们公司要求的用的是MD5加密的方式,由于它是普通的散列,上网一百度就能看到答案,所以我需要重写一下PasswordEncoder。

    同样的我们需要实现这个接口,我们需要重写两个方法encode()、matches()。一眼看上去不难发现,matches一定是用户认证的,那我们就可以把重点放到这个方法下了。

    因为是例子,所以我写的比较简单了,显而易见我用的Spring自带的MD5加密工具了。

    当然,spring-security是比较推荐 BCryptPasswordEncoder,我也象征性的看了一下源码并不是很难理解大家可以去看下。

  • 相关阅读:
    阅读笔记7
    阅读笔记6
    架构阅读笔记5
    软件质量属性——易用性课堂讨论问题总结
    Git 的 .gitignore 配置
    zookeeper的简单搭建,java使用zk的例子和一些坑
    MySQL中有关TIMESTAMP和DATETIME的对比
    Mysql 如何设置字段自动获取当前时间,附带添加字段和修改字段的例子
    spring boot注入error,Consider defining a bean of type 'xxx' in your configuration问题解决方案
    net start命令发生系统错误5和错误1058的解决方法
  • 原文地址:https://www.cnblogs.com/Tiandaochouqin1/p/10979063.html
Copyright © 2011-2022 走看看