springboot+security 01 - 实现权限控制

参考博客: https://blog.csdn.net/Canon_in_D_Major/article/details/79688441

仅用于spring security个人学习笔记

  1. 当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。
  2. 程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。
  3. 用户访问一个资源的时候,首先判断是否是受限资源。如果是的话还要判断当前是否未登录,没有的话就跳到登录页面。
  4. 如果用户已经登录,访问一个受限资源的时候,程序要根据url去数据库中取出该资源所对应的所有可以访问的角色,然后拿着当前用户的所有角色一一对比,判断用户是否可以访问。
  • sb_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 http://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>1.5.20.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.youxiu326</groupId>
    <artifactId>sb_security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sb_security</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
    </properties>

    <dependencies>

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

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

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

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

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

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

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

</project>
pom.xml
server:
  port: 8080
  context-path: /


spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://youxiu326.xin:3306/security?useUnicode=true&characterEncoding=utf8
    username: youxiu326
    password: zz123456.ZZ

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update

  thymeleaf:
    cache: false
application.yml
package com.youxiu326.bean;


import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

@Entity
@Table(name="sys_user")
public class User implements Serializable {

    private String id;

    private String userName;

    private String password;

    /**
     * 拥有角色
     */
    private List<Role> roles;

    @Id
    @GeneratedValue(generator = "sys_uid")
    @GenericGenerator(name = "sys_uid", strategy = "uuid")
    public String getId() {
        return id;
    }

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

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

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

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "sys_user_role", joinColumns = {
            @JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = {
            @JoinColumn(name = "role_id", referencedColumnName = "id") })
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}
User.java
package com.youxiu326.bean;


import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

@Entity
@Table(name="sys_role")
public class Role implements Serializable {

    private String id;

    private String roleName;

    private String roleNameZh;

    /**
     * 角色拥有资源
     */
    private List<Resource> resources;

    @Id
    @GeneratedValue(generator = "sys_uid")
    @GenericGenerator(name = "sys_uid", strategy = "uuid")
    public String getId() {
        return id;
    }

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

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleNameZh() {
        return roleNameZh;
    }

    public void setRoleNameZh(String roleNameZh) {
        this.roleNameZh = roleNameZh;
    }

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "sys_role_resource", joinColumns = {
            @JoinColumn(name = "role_id", referencedColumnName = "id") },
            inverseJoinColumns = {@JoinColumn(name = "resource_id", referencedColumnName = "id") })
    public List<Resource> getResources() {
        return resources;
    }

    public void setResources(List<Resource> resources) {
        this.resources = resources;
    }
}
Role.java
package com.youxiu326.bean;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.GenericGenerator;

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

@Entity
@Table(name="sys_Resource")
public class Resource implements Serializable {

    private String id;

    private String url;

    private String resName;

    /**
     * 上级权限
     */
    private Resource parent;

    /**
     * 下级权限
     */
    private List<Resource> children;

    @Id
    @GeneratedValue(generator = "sys_uid")
    @GenericGenerator(name = "sys_uid", strategy = "uuid")
    public String getId() {
        return id;
    }

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

    public String getUrl() {
        return url;
    }

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

    public String getResName() {
        return resName;
    }

    public void setResName(String resName) {
        this.resName = resName;
    }

    @ManyToOne
    @JoinColumn(name = "parent_id")
    @JsonIgnore
    public Resource getParent() {
        return parent;
    }

    public void setParent(Resource parent) {
        this.parent = parent;
    }

    /*
     * 级联删除
     * @return
     */
    @OneToMany(cascade={CascadeType.REMOVE},mappedBy = "parent")
    public List<Resource> getChildren() {
        return children;
    }

    public void setChildren(List<Resource> children) {
        this.children = children;
    }
}
Resource.java
sys_user   

1	user	$2a$10$cr.EMPhNHbBX8Xksa62gGeo1cwPjk0rp4tDc5ICLAhY8.y1BHUjxK
2	admin	$2a$10$cr.EMPhNHbBX8Xksa62gGeo1cwPjk0rp4tDc5ICLAhY8.y1BHUjxK


sys_role

1	user	普通用户
2	admin	管理员

sys_resource

1	用户页面	/userPage	
2	管理员页面	/adminPage	
3	所有人可看页面	/allPage	


sys_user_role

1	1
2	2

sys_role_resource

1	1
2	2

简单描述一下

两个用户    code:user    password:111111
           code:admin  password:111111

user   拥有user 普通用户角色
admin 拥有admin管理员角色

user角色拥有    /userPage   资源(即user用户可以访问该资源)
admin角色拥有  /adminPage 资源 (即admin用户可以访问该资源)
package com.youxiu326.service;

import com.youxiu326.bean.Resource;
import com.youxiu326.bean.Role;

import java.util.List;

public interface ResourceService {

    /**
     * 根据访问资源路径 查询资源对象
     * @param url 资源路径
     * @return
     */
    Resource getResourceByUrl(String url);

    /**
     * 根据资源id 查询所有拥有该资源的角色
     * @param resourceId 资源id
     * @return
     */
    List<Role> getRoles(String resourceId);

} 
ResourceService.java
package com.youxiu326.service;

import com.youxiu326.bean.Role;

import java.util.List;

public interface RoleService {

    /**
     * 根据用户code查询该用户拥有的所有角色
     * @param userName 用户code
     * @return
     */
    List<Role> getRolesOfUser(String userName);

} 
RoleService.java
package com.youxiu326.service.impl;

import com.youxiu326.bean.Resource;
import com.youxiu326.bean.Role;
import com.youxiu326.repository.ResourceDao;
import com.youxiu326.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ResourceServiceImpl implements ResourceService {

    @Autowired
    private ResourceDao resourceDao;

    @Override
    public Resource getResourceByUrl(String url) {
        return resourceDao.findByUrl(url);
    }

    @Override
    public List<Role> getRoles(String resourceId) {
        return resourceDao.findRolesOfResource(resourceId);
    }
}
ResourceServiceImpl.java
package com.youxiu326.service.impl;

import com.youxiu326.bean.Role;
import com.youxiu326.repository.RoleDao;
import com.youxiu326.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    private RoleDao roleDao;

    @Override
    public List<Role> getRolesOfUser(String userName) {
        return roleDao.findRolesByUser(userName);
    }

}
RoleServiceImpl.java
package com.youxiu326.repository;

import com.youxiu326.bean.Resource;
import com.youxiu326.bean.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;

/**
 * Created by lihui on 2019/1/29.
 */
public interface ResourceDao extends JpaRepository<Resource, String> {

    @Query("select distinct r.resources from User as o left join o.roles as r where o.id = ?1")
    public List<Resource> findResourcesByOperator(String operatorId);

    Resource findByUrl(String url);

    //自定义sql语句并且开启本地sql
    //根据用户名查找该用户所有权限
    //@Query(value = "select r.* from role r, user_role ur where ur.username = ?1 and ur.rid = r.id", nativeQuery = true)
    //public List<Role> findRolesOfUser(String username);

    //根据resource的主键查找resource允许的所有权限
    @Query("select r from Role r left join r.resources as rs where rs.id=?1 ")
    public List<Role> findRolesOfResource(String resourceId);

}
ResourceDao.java
package com.youxiu326.repository;

import com.youxiu326.bean.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface RoleDao extends JpaRepository<Role, String> {


    /*
    @Query("select distinct r.resources from Operator as o left join o.roles as r where o.id = ?1")
     public List<Resource> findResourcesByOperator(String operatorId);
    */

    @Query("select distinct r.roles from User as r where r.userName=?1")
    List<Role> findRolesByUser(String userName);

}
RoleDao.java
package com.youxiu326.repository;

import com.youxiu326.bean.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;


public interface UserDao extends JpaRepository<User, String> {

    List<User> findByUserName(String userName);

}
UserDao.java

为框架实现UserDetails接口和UserDetailsService接口在我们的程序中,必须要有一个类,实现UserDetailsService这个接口并且重写它的loadUserByUsername(String s)这个函数。另外也必须要有一个类,实现UserDetails接口并重写它其中的几个方法。为什么呢?这涉及到Spring Security框架的认证的原理。在用户登录的时候,程序将用户输入的的用户名和密码封装成一个类对象。然后根据用户名去数据库中查找用户的数据,封装成一个类对象放在内存中。注意,一个是用户输入的数据,一个是数据库中的数据。将两个对象比对,如果密码正确,就把用户信息的封装(包含着身份信息、细节信息等)存到SecurityContextHolder中(类似Session),使用的时候还要取出来。而这个过程中,从数据库中取出的用户信息的封装不是简单的User实例,而是一个实现了UserDetails这个接口的类的对象,这个对象里面不仅有用户的账号密码信息,还有一些判断账号是否可用、判断账号是否过期、判断账号是否被锁定的函数。在验证过程中,负责根据用户输入的用户名返回数据库中用户信息的封装这个功能的就是Service,它实现了UserDetailsService,重写了它的loadUserByUsername(String s)方法,这个方法就是根据用户名返回了UserDetails的一个具体实现

package com.youxiu326.security;

import com.youxiu326.bean.Role;
import com.youxiu326.bean.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

//一定要有一个类,实现UserDetails接口,供程序调用
public class UserDetailsImpl implements UserDetails {

    private String username;
    private String password;
    //包含着用户对应的所有Role,在使用时调用者给对象注入roles
    private List<Role> roles;

    public UserDetailsImpl() {
    }

    //用User构造
    public UserDetailsImpl(User user) {
        this.username = user.getUserName();
        this.password = user.getPassword();
    }

    //用User和List<Role>构造
    public UserDetailsImpl(User user, List<Role> roles) {
        this.username = user.getUserName();
        this.password = user.getPassword();
        this.roles = roles;
    }

    @Override
    //返回用户所有角色的封装,一个Role对应一个GrantedAuthority
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for(Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        return authorities;
    }

    public List<Role> getRoles()
    {
        return roles;
    }

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

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    //判断账号是否已经过期,默认没有过期
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    //判断账号是否被锁定,默认没有锁定
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    //判断信用凭证是否过期,默认没有过期
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    //判断账号是否可用,默认可用
    public boolean isEnabled() {
        return true;
    }

}

UserDetailsService也需要被实现,我们在写UserService时直接实现这个接口就可以。所以UserService跟其他Service有些不同

package com.youxiu326.security;

import com.youxiu326.bean.User;
import com.youxiu326.repository.UserDao;
import com.youxiu326.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
import javax.transaction.Transactional;
import java.util.List;


/**
 * 简单来讲就是程序接收到了用户输入的用户名,交给了UserService,它根据用户名去数据库中取到用户的信息,
 * 封装到实体类User的实例中,然后使用该User实例,再利用RoleService(封装了RoleRopository)查出该User对用的roles,
 * 构造一个UserDetailsImpl的对象,把这个对象返回给程序
 */
@Service
//框架需要使用到一个实现了UserDetailsService接口的类
public class MyUserDetailsService implements UserDetailsService{


    @Autowired
    private UserDao userRepository;

    @Autowired
    private RoleService roleService;

    @Transactional
    public List<User> getAllUser()
    {
        return userRepository.findAll();
    }

    @Transactional
    public List<User> getByUsername(String userName)
    {
        return userRepository.findByUserName(userName);
    }


    @Override
    //重写UserDetailsService接口里面的抽象方法
    //根据用户名 返回一个UserDetails的实现类的实例
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        System.out.println("查找用户:" + s);
        List<User> list = getByUsername(s);
        if (list==null || list.size()==0){
            throw new UsernameNotFoundException("没有该用户");
        }
        User user = getByUsername(s).get(0);
        if(user == null)
        {
            throw new UsernameNotFoundException("没有该用户");
        }

        //查到User后将其封装为UserDetails的实现类的实例供程序调用
        //用该User和它对应的Role实体们构造UserDetails的实现类
        return new UserDetailsImpl(user, roleService.getRolesOfUser(user.getUserName()));
    }

}

1.写一个类,实现FilterInvocationSecurityMetadataSource这个接口,供系统调用,放在Component包中。作用是在用户请求一个地址的时候,截获这个地址,告诉程序访问这个地址需要哪些权限角色。

package com.youxiu326.security;
import com.youxiu326.bean.Resource;
import com.youxiu326.bean.Role;
import com.youxiu326.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;

@Component
//接收用户请求的地址,返回访问该地址需要的所有权限
public class MyFilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private ResourceService resourceService;

    @Override
    //接收用户请求的地址,返回访问该地址需要的所有权限
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //得到用户的请求地址,控制台输出一下
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        System.out.println("用户请求的地址是:" + requestUrl);

        //如果登录页面就不需要权限
        if ("/login".equals(requestUrl)) {
            return null;
        }

        Resource resource = resourceService.getResourceByUrl(requestUrl);

        //如果没有匹配的url则说明大家都可以访问
        if(resource == null) {
            return SecurityConfig.createList("ROLE_LOGIN");
        }

        //将resource所需要到的roles按框架要求封装返回(ResourceService里面的getRoles方法是基于RoleRepository实现的)
        List<Role> roles = resourceService.getRoles(resource.getId());
        int size = roles.size();
        String[] values = new String[size];
        for (int i = 0; i < size; i++) {
            values[i] = roles.get(i).getRoleName();
        }
        return SecurityConfig.createList(values);
    }

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

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }

} 

2.写一个类,实现AccessDecisionManager这个接口  这个类的作用是接收上面那个类返回的访问当前url所需要的权限列表(decide方法的第三个参数),再结合当前用户的信息(decide方法的第一个参数),决定用户是否可以访问这个url

package com.youxiu326.security;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;

@Component
//Security需要用到一个实现了AccessDecisionManager接口的类
//类功能:根据当前用户的信息,和目标url涉及到的权限,判断用户是否可以访问
//判断规则:用户只要匹配到目标url权限中的一个role就可以访问
public class MyAccessDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

        //迭代器遍历目标url的权限列表
        Iterator<ConfigAttribute> iterator = collection.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();

            String needRole = ca.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登录");
                } else
                    return;
            }

            //遍历当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }

        //执行到这里说明没有匹配到应有权限
        throw new AccessDeniedException("权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

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

3.写一个类,实现AccessDeniedHandler这个接口  作用是自定义403响应内容。

package com.youxiu326.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
//自定义403响应内容
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
        out.flush();
        out.close();
    }

}

4.写一个Spring Security配置类,继承WebSecurityConfigurerAdapter

package com.youxiu326.config;

import com.youxiu326.security.MyAccessDecisionManager;
import com.youxiu326.security.MyAccessDeniedHandler;
import com.youxiu326.security.MyFilterInvocationSecurityMetadataSourceImpl;
import com.youxiu326.security.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true) //开启方法权限控制
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    //根据一个url请求,获得访问它所需要的roles权限
    @Autowired
    private MyFilterInvocationSecurityMetadataSourceImpl myFilterInvocationSecurityMetadataSource;

    //接收一个用户的信息和访问一个url所需要的权限,判断该用户是否可以访问
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;

    //403页面
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    /**定义认证用户信息获取来源,密码校验规则等*/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**有以下几种形式,使用第3种*/
        //inMemoryAuthentication 从内存中获取
        //auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123123")).roles("USER");

        //jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构
        //usersByUsernameQuery 指定查询用户SQL
        //authoritiesByUsernameQuery 指定查询权限SQL
        //auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);

        //注入userDetailsService,需要实现userDetailsService接口
        auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    //在这里配置哪些页面不需要认证(静态文件这些不需要认证)
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/", "/noAuthenticate","/**/*.ico","/**/*.js");
    }

    /**定义安全策略*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()       //配置安全策略
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(myAccessDecisionManager);
                        return o;
                    }
                })
//                .antMatchers("/hello").hasAuthority("ADMIN")
                //.antMatchers("/js/**","/css/**","/images/*","/fonts/**","/**/*.png","/**/*.jpg","/**/*.ico").permitAll()
                .and()
                .formLogin()
                .loginPage("/login")//自定义登录页面 //未登录时跳转至 localhost:8080/login
                .loginProcessingUrl("/user/login")// 自定义的登录接口(login.html 登陆请求接口 即是 localhost:8080/user/login)
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        StringBuffer sb = new StringBuffer();
                        sb.append("{\"status\":\"error\",\"msg\":\"");
                        if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
                            sb.append("用户名或密码输入错误,登录失败!");
                        } else if (e instanceof DisabledException) {
                            sb.append("账户被禁用,登录失败,请联系管理员!");
                        } else {
                            sb.append("登录失败!");
                        }
                        sb.append("\"}");
                        out.write(sb.toString());
                        out.flush();
                        out.close();
                    }
                })
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
//                        ObjectMapper objectMapper = new ObjectMapper();
                        //String s = "{\"status\":\"success\",\"msg\":"  + "}";
                        String s = "{\"status\":\"200\",\"data\":\"success\",\"msg\":\"登录成功\"}";
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                .and()
                .logout()
                .permitAll()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder  auth) throws  Exception {
        auth.userDetailsService(myUserDetailsService)
                .passwordEncoder(passwordEncoder());

    }


    /**
     * 加密方式
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

所有配置类都已写完,写页面 和 Controller

package com.youxiu326.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SecurityController {

    /**
     * 自定义登录界面
     * @return
     */
    @GetMapping(value = "/login")
    public String login(){
        return "login";
    }


    /**
     * admin角色可访问
     * @return
     */
    @GetMapping(value = "/adminPage")
    public String adminPage(){
        return "adminPage";
    }
    /**
     * user角色可访问
     * @return
     */
    @GetMapping(value = "/userPage")
    public String userPage(){
        return "userPage";
    }
    /**
     * 所有人可访问 该资源在资源表中无记录
     * @return
     */
    @GetMapping(value = "/allPage")
    public String allPage(){
        return "/allPage";
    }

}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <base th:href="${#httpServletRequest.getContextPath()+'/'}">
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>自定义登录页面</h2>
<form action="#" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" id="username" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" id="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><button type="button" onclick="login()">登录</button></td>
        </tr>
    </table>

</form>
</body>

<script src="/jquery-1.11.3.min.js"></script>
<!--<script th:src="@{/jquery-1.11.3.min.js}"></script>-->
<script>
    function login(){
        $.ajax({
            type: 'POST',
            url: "/user/login",
            data: {"username":$("#username").val(),"password":$("#password").val()},
            // dataType: "json",
            success: function(response){
                if(response.data=="success"){
                    window.location.href="allPage";
                }else {
                    alert(response.msg);
                }
                console.log(response);
            },
            error:function(response){
                alert("失败");
                console.log(response);
            }
        });
    }
</script>


</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <base th:href="${#httpServletRequest.getContextPath()+'/'}">
    <meta charset="UTF-8">
    <title>所有人可看页面</title>
</head>
<body>
<h2>这是allPage页面</h2>

<br/>

<a href="userPage" target="_blank">去用户界面</a>

<br/>

<a href="adminPage" target="_blank">去管理员界面</a>

</body>
</html>
adminPage.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <base th:href="${#httpServletRequest.getContextPath()+'/'}">
    <meta charset="UTF-8">
    <title>用户页面界面</title>
</head>
<body>
<h2>这是userPage页面</h2>

</body>
</html>
userPage.java
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <base th:href="${#httpServletRequest.getContextPath()+'/'}">
    <meta charset="UTF-8">
    <title>所有人可看页面</title>
</head>
<body>
<h2>这是allPage页面</h2>

<br/>

<a href="userPage" target="_blank">去用户界面</a>

<br/>

<a href="adminPage" target="_blank">去管理员界面</a>

</body>
</html>
allPage.html

大功告成,测试开始

1.未登录时访问资源路径 自动跳转至登陆页面(localhost:8080/login)

2.输入错误的用户名or密码 提示错误信息

3.登陆时请求路径(localhost:8080/user/login)

4.登陆后user用户可以访问http://localhost:8080/userPage 但是无权访问http://localhost:8080/adminPage

 

 

5.未登录时是可以访问http://localhost:8080/allPage (因为该资源未在资源表中有记录)

github: https://github.com/youxiu326/sb_security.git

猜你喜欢

转载自www.cnblogs.com/youxiu326/p/spring-security-01.html