關(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)

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ù):

簡(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

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

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

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