简单认证授权
项目结构图
build.gradle
plugins {
id 'org.springframework.boot' version '2.1.16.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'war'
}
group = 'com.sky'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter'}
maven { url 'https://repo.spring.io/snapshot' }
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test{
//useJUnitPlatform()
}
application.yml
server:
port: 8081
SpringBootGradleApplication
//spring security 启动方法级别的权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringBootGradleApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(SpringBootGradleApplication.class, args);
}
/** 使用外置的tomcat启动,需要继承SpringBootServletInitializer */
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootGradleApplication.class);
}
}
SysUser 用户信息,用于登录
/**
* 用户信息,用于登录
* @author Administrator
*
*/
public class SysUser {
private int id;
private String username;
private String password;
private List<String> roles;
public SysUser() {}
public SysUser(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
//...省略get set
public SysUser addRole(String roleName) {
if(this.roles == null) {
this.roles = new ArrayList<>();
}
roles.add(roleName);
return this;
}
}
用户service类,构造用户数据,并实现UserDetailsService
public interface SysUserService {
/** 根据账号获取用户 */
SysUser getUserByAccount(String account);
}
@Service
public class SysUserServiceImpl implements SysUserService , UserDetailsService {
private static Map<String, SysUser> USER_MAP = new HashMap<>(16);
static{
//密码都是123456
String encodePass = "{bcrypt}$2a$10$t8D4CmkVOCx834key9Mz5OL0M3MJlgd2c4hV3dxfl4zTXgJG1nJu.";
USER_MAP.put("admin", new SysUser(1,"admin",encodePass).addRole("ROLE_EMPLOYEE").addRole("ROLE_ADMIN"));
USER_MAP.put("lisa", new SysUser(2,"lisa",encodePass).addRole("ROLE_EMPLOYEE"));
USER_MAP.put("zhaosi", new SysUser(3,"zhaosi",encodePass));
}
@Override
public SysUser getUserByAccount(String account) {
return USER_MAP.get(account);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
throw new UsernameNotFoundException("UserDetailsService没有接收到用户账号");
}
SysUser sysUser = getUserByAccount(username);
if (sysUser == null) {
throw new UsernameNotFoundException(String.format("用户'%s'不存在", username));
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String role : sysUser.getRoles()) {
// 封装用户信息和角色信息到SecurityContextHolder全局缓存中
grantedAuthorities.add(new SimpleGrantedAuthority(role));
}
return new User(sysUser.getUsername(), sysUser.getPassword(), grantedAuthorities);
}
//@Override
public UserDetails loadUserByUsernameBak(String username) throws UsernameNotFoundException {
//使用明文密码,默认的加密方式
//UserDetails user = User.withDefaultPasswordEncoder().username("admin").password("123456").roles("USER_ROLE").build();
//使用明文密码,需要指定加密方式
//UserDetails user = User.withUsername("admin").passwordEncoder(passwordEncoder::encode).password("123456").roles("USER_ROLE").build();
//使用加密密码
UserDetails user = User.withUsername("admin").password("{bcrypt}$2a$10$t8D4CmkVOCx834key9Mz5OL0M3MJlgd2c4hV3dxfl4zTXgJG1nJu.").roles("USER_ROLE").build();
return user;
}
}
SecurityConfig:spring security的配置类
@Configuration
@EnableWebSecurity
@Order(1)// 如果配置了多个WebSecurityConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired PasswordEncoder passwordEncoder;
@Bean
PasswordEncoder passwordEncoder() {
//Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/** 忽略拦截 */
@Override
public void configure(WebSecurity web) throws Exception {
// 直接过滤掉该地址,即该地址不走 Spring Security 过滤器链
web.ignoring().antMatchers("/ignore","/other");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//匹配的资源任何人都可以访问
http.authorizeRequests().antMatchers("/", "/home").permitAll();
//访问任何资源都需要通过身份验证
http.authorizeRequests().anyRequest().authenticated();
//指定登录页面,任何人都可以访问
http.authorizeRequests().and().formLogin()
.loginPage("/login")//指定登录页面, spring security有默认的登录页面
//.successHandler(authenticationSuccessHandler())//登录成功处理,不需要可忽略
//.failureHandler(authenticationFailureHandler())//登录失败处理,不需要可忽略
.permitAll().and();
//指定登出页面,任何人都可以访问
http.authorizeRequests().and().logout().permitAll().and();
}
/** 登录成功处理,不需要可忽略 */
@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return (request,response, authentication) -> {
System.err.println("登陆成功");
//这里直接写自己的处理逻辑,比如下面这段代码
response.setContentType("application/json;charset=UTF-8");
PrintWriter out=response.getWriter();
out.write("一个JSON串");
};
}
/** 登录失败处理,不需要可忽略 */
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return (request,response, e) -> {
System.err.println("登陆失败");
};
}
}
MvcConfig
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/ignore").setViewName("ignore");
}
}
controller类用于测试
@RestController
public class UserController {
/**
* 获取登录后的Principal(需要登录)
*/
@GetMapping("/getPrincipal")
public Object getPrincipal(@AuthenticationPrincipal Principal principal){
return principal;
}
/**
* 获取登录后的UserDetails(需要登录)
*/
@GetMapping("/getUserDetails")
public Object getUserDetails(@AuthenticationPrincipal UserDetails userDetails) {
return userDetails;
}
}
@Controller
@RequestMapping("roletest")
public class RoleTestController {
/**
* 跳转到admin.html页面(需要登录,且需要ROLE_ADMIN角色)
*/
@GetMapping("/admin")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String admin() {
return "admin.html";
}
/**
* 跳转到admin.html页面(需要登录,且需要ROLE_ADMIN角色)
*/
@GetMapping("/employee")
@PreAuthorize("hasRole('ROLE_EMPLOYEE')")
public String employee() {
return "employee.html";
}
}
相关html页面
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
<a target="_blank" th:href="@{/getUserDetails}">用户详情</a><br/>
<a target="_blank" th:href="@{/getPrincipal}">用户最重要信息</a><br/>
<a target="_blank" th:href="@{/roletest/admin}">管理员能看的页面</a><br/>
<a target="_blank" th:href="@{/roletest/employee}">员工能看的页面</a><br/>
</body>
</html>
ignore.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
直接过滤掉该地址,即该地址不走 Spring Security 过滤器链
</body>
</html>
home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>home.html</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>
admin.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>admin.html</title>
</head>
<body>
需要登录,且需要ROLE_ADMIN角色
</body>
</html>
employee.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>employee.html</title>
</head>
<body>
需要登录,且需要ROLE_EMPLOYEE角色
</body>
</html>
测试
http://localhost:8081/
该页面不需身份认证,点击here,跳转到 /hello
因为 /hello需要身份认证,所以转跳到 /login
使用错误密码登录
成功登录后
点击登出,重新回到登录页面
重新登录后
点击here,跳转到 /hello,然后分别点击最后4个连接
lisa不是管理员所以没有权限查看