認(rèn)證與授權(quán)(二):搭建Spring Cloud Security單體應(yīng)用

關(guān)于認(rèn)證與授權(quán)相關(guān)概念介紹,請(qǐng)參考:http://www.itdecent.cn/p/6323a2d63284

數(shù)據(jù)庫(kù)新建user表

create table tc_user(
    ID nvarchar (50) not null,
    NAME nvarchar (20) null,
    PWD varchar (50) null,
    ROLE char (5) null,
    SSBM char (5) null,
    PHONE nvarchar (20) null,
    BZ nvarchar (100) null,
    state char (1) null,
    constraint  PK_TC_User primary key (ID asc)
)

新建maven項(xiàng)目,項(xiàng)目結(jié)構(gòu)


項(xiàng)目結(jié)構(gòu).jpg

pom.xml

<?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.2.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ljessie</groupId>
    <artifactId>control-security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>control-security</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc -->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>8.2.2.jre8</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>


    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

resources文件夾下新建application.yml

spring:
    application:
        name: authorizationServer
    datasource:
        url: jdbc:sqlserver://localhost:1433;instanceName=sql2005;DatabaseName=k_record
        username: sa
        password: 123456
        driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver

server:
    port: 8766

mybatis:
    mapper-locations: classpath:mappers/*.xml
    config-location: classpath:mybatis-config.xml

mybatis-config.xml,mybatis相關(guān)配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!--    mapUnderscoreToCamelCase:開啟駝峰命名
        logImpl:控制臺(tái)打印Sql語(yǔ)句-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <typeAliases>
        <package name="com.ljessie.controlsecurity"/>
    </typeAliases>
</configuration>

ControlSecurityApplication,配置mybatis掃描包注解

package com.ljessie.controlsecurity;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.ljessie.controlsecurity.mapper")
public class ControlSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(ControlSecurityApplication.class, args);
    }
}

新建實(shí)體類User

package com.ljessie.controlsecurity.entity;

import java.util.Set;

public class User {

    private String id;
    private String name;
    private String pwd;
    private String role;

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    /**
     * 用戶權(quán)限
     */
    private Set<String> authorities;

    public Set<String> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(Set<String> authorities) {
        this.authorities = authorities;
    }

    public User(String id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", password='" + pwd + '\'' +
                ", role='" + role + '\'' +
                ", authorities=" + authorities +
                '}';
    }

    public User() {
    }

    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;
    }

    public String getPwd() {
        return pwd;
    }

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

新建JsonData用來(lái)封裝返回的數(shù)據(jù)

package com.ljessie.controlsecurity.entity;

import java.io.Serializable;

public class JsonData implements Serializable {
    private static final long serialVersionUID = 1L;

    //狀態(tài)碼,0表示成功,-1表示失敗
    private int code;
    
    //結(jié)果
    private Object data;

    //錯(cuò)誤描述
    private String msg;
    
    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public JsonData(int code, Object data) {
        super();
        this.code = code;
        this.data = data;
    }

    public JsonData(int code, String msg, Object data) {
        super();
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

新建UserMapper,去數(shù)據(jù)庫(kù)查詢User

package com.ljessie.controlsecurity.mapper;

import com.ljessie.controlsecurity.entity.User;

public interface UserMapper {

    /**
     * 根據(jù)id查詢用戶
     * @param id
     * @return
     */
    User findUserById(String id);
}

resources文件夾下,新建mappers文件夾,再新建UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ljessie.controlsecurity.mapper.UserMapper">
    <select id="findUserById" resultType="User">
        select id,name,pwd,role
            from tc_user
            where id = #{id}
    </select>
</mapper>

然后新建UserService,實(shí)現(xiàn)UserDetailsService接口

package com.ljessie.controlsecurity.service;

import com.ljessie.controlsecurity.entity.User;
import com.ljessie.controlsecurity.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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 org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if(StringUtils.isEmpty(username)){
            throw new RuntimeException("用戶名不能為空");
        }

        User userById = userMapper.findUserById(username);
        if(userById == null){
            throw new RuntimeException("用戶不存在");
        }

        //根據(jù)用戶的角色設(shè)置用戶的權(quán)限
        List<GrantedAuthority> permissions = new ArrayList<>();
        permissions.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return userById.getRole();
            }
        });

        return new org.springframework.security.core.userdetails.User(userById.getId(),userById.getPwd(),permissions);
    }
}

用戶表數(shù)據(jù):


用戶表.jpg

簡(jiǎn)單起見,UserService里面將role設(shè)置為用戶擁有的權(quán)限。數(shù)據(jù)庫(kù)密碼保存的是用Spring Security默認(rèn)的BCryptPasswordEncoder密碼編碼器哈希過(guò)的值,如果是普通的字符串,待會(huì)將會(huì)報(bào)錯(cuò)。新建測(cè)試類,生成哈希值,保存到數(shù)據(jù)庫(kù)里面。

package com.ljessie.controlsecurity;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCrypt;

@SpringBootTest
class ControlSecurityApplicationTests {

    @Test
    void contextLoads() {
        //對(duì)密碼進(jìn)行加密
        String hashpw = BCrypt.hashpw("123456", BCrypt.gensalt());
        System.out.println(hashpw);

        //校驗(yàn)密碼
        boolean checkpw = BCrypt.checkpw("123456", "$2a$10$vS1iAP/NFOEu5aB0nIxJY.CRaVclRWqHPy3eCCst3gKMvofLjECEi");
        System.out.println(checkpw);

    }
}

TestPermissionController

package com.ljessie.controlsecurity.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("permission_test")
public class TestPermissionController {

    @RequestMapping("/resource1")
    public String resource1(){
        return "只有00001用戶才能訪問(wèn)的資源";
    }

    @RequestMapping("/resource2")
    public String resource2(){
        return "只有00002用戶才能訪問(wèn)的資源";
    }
}

配置訪問(wèn)拒絕端點(diǎn)AccessDeniedEntryPoint,當(dāng)用戶沒有權(quán)限訪問(wèn)資源時(shí),返回自定義的數(shù)據(jù)。
AccessDeniedEntryPoint

package com.ljessie.controlsecurity.handler;

import com.alibaba.fastjson.JSON;
import com.ljessie.controlsecurity.entity.JsonData;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

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

@Component
public class AccessDeniedEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        JsonData jsonData = new JsonData(-1,"請(qǐng)登錄先",null);
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(jsonData));
    }
}

配置登錄失敗處理器,用戶登錄失敗時(shí),返回自定義數(shù)據(jù)
LoginFailureHandler

package com.ljessie.controlsecurity.handler;

import com.alibaba.fastjson.JSON;
import com.ljessie.controlsecurity.entity.JsonData;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        //返回json數(shù)據(jù)
        JsonData result = null;
        if (e instanceof AccountExpiredException) {
            //賬號(hào)過(guò)期
            result = new JsonData(-1,"賬號(hào)過(guò)期");
        } else if (e instanceof BadCredentialsException) {
            //密碼錯(cuò)誤
            result = new JsonData(-1,"密碼錯(cuò)誤");
        } else if (e instanceof CredentialsExpiredException) {
            //密碼過(guò)期
            result = new JsonData(-1,"密碼過(guò)期");
        } else if (e instanceof DisabledException) {
            //賬號(hào)不可用
            result = new JsonData(-1,"賬號(hào)不可用");
        } else if (e instanceof LockedException) {
            //賬號(hào)鎖定
            result = new JsonData(-1,"賬號(hào)鎖定");
        } else if (e instanceof InternalAuthenticationServiceException) {
            //用戶不存在
            result = new JsonData(-1,"用戶不存在");
        }else{
            //其他錯(cuò)誤
            result = new JsonData(-1,"其他錯(cuò)誤");
        }
        //處理編碼方式,防止中文亂碼的情況
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回給前臺(tái)
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

登錄成功處理器:LoginSuccessHandler

package com.ljessie.controlsecurity.handler;

import com.alibaba.fastjson.JSON;
import com.ljessie.controlsecurity.entity.JsonData;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

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

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        JsonData result = new JsonData(1,"登錄成功",principal);
        //處理編碼方式,防止中文亂碼的情況
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回給前臺(tái)
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

退出登錄處理器:MyLogoutSuccessHandler

package com.ljessie.controlsecurity.handler;

import com.alibaba.fastjson.JSON;
import com.ljessie.controlsecurity.entity.JsonData;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

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

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        JsonData result = new JsonData(1,"退出登錄");
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

下面就是Spring Security的核心配置類WebSecurityConfig

package com.ljessie.controlsecurity.config;

import com.ljessie.controlsecurity.handler.AccessDeniedEntryPoint;
import com.ljessie.controlsecurity.handler.LoginFailureHandler;
import com.ljessie.controlsecurity.handler.LoginSuccessHandler;
import com.ljessie.controlsecurity.handler.MyLogoutSuccessHandler;
import com.ljessie.controlsecurity.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    AccessDeniedEntryPoint entryPoint;

    @Autowired
    LoginSuccessHandler loginSuccessHandler;

    @Autowired
    LoginFailureHandler loginFailureHandler;

    @Autowired
    MyLogoutSuccessHandler logoutSuccessHandler;

    /**
     * 密碼編輯器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //關(guān)閉csrf攻擊防御
                .csrf().disable()
                //對(duì)路徑攔截進(jìn)行配置
                .authorizeRequests()
                //配置/permission_test/resource1路徑需要00001權(quán)限
                .antMatchers("/permission_test/resource1").hasAuthority("00001")
                .antMatchers("/permission_test/resource2").hasAuthority("00002")
                //異常處理(權(quán)限拒絕,登錄失效等)
                .and().exceptionHandling()
                .authenticationEntryPoint(entryPoint)
                //登錄
                .and().formLogin()
                .permitAll()
                .successHandler(loginSuccessHandler)//登錄成功處理邏輯
                .failureHandler(loginFailureHandler)//登錄失敗處理邏輯;//允許所有用戶;
                //退出登錄
                .and().logout().
                permitAll()//允許所有用戶
                .logoutSuccessHandler(logoutSuccessHandler)//登出成功處理邏輯
                .deleteCookies("JSESSIONID")  //登出之后刪除cookie
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/*").permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置認(rèn)證方式
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Bean
    public UserDetailsService userDetailsService(){
        return new UserService();
    }
}

啟動(dòng)應(yīng)用,使用postman直接訪問(wèn):http://localhost:8766/permission_test/resource1

test_permission1.jpg

當(dāng)訪問(wèn)資源被拒絕時(shí),返回自定義的Json數(shù)據(jù)。
訪問(wèn)Spring Security自帶的登錄接口http://localhost:8766/login,請(qǐng)求方式為POST,登錄role為00001的用戶
login_success.jpg

再次訪問(wèn)http://localhost:8766/permission_test/resource1
test_permission_success.jpg

訪問(wèn)resource2接口http://localhost:8766/permission_test/resource2,返回403,role為00001的用戶沒有權(quán)限訪問(wèn)resource2接口
test_permission2.jpg

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容