SpringSecurity解决两件事情:用户认证(谁可以登陆)、用户授权(他可以干啥)
springboot+springsecurity+thymeleaf+springdatajpa+mysql
用户信息保存在mysql数据库所以需要MySQL驱动,持久化选择spring-data-jpa,从内存认证可以不用考虑持久化这一块
版本信息在pom文件可见,springboot版本过高会出现奇奇怪怪的问题
pom.xml
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.9.RELEASEversion>
<relativePath/>
parent>
<groupId>com.dandangroupId>
<artifactId>springboot-06-securityartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springboot-06-securityname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.20version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>3.0.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
user实体类,这个表结构假装一个用户只有一个角色,否则应该有用户表、角色表、用户角色中间表,这样写只是为了简单
package com.dandan.entity;
import lombok.Data;
import javax.persistence.*;
@Data
@Table(name = "user")
@Entity
public class UserEntity {
@Id // 表明id
@GeneratedValue
String id;
@Column(name = "user_name")
String username;
@Column(name = "user_password")
String password;
@Column(name = "roles")
String roles;
}
userDao
package com.dandan.dao;
import com.dandan.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserDao extends JpaRepository<UserEntity,String> {
UserEntity findByUsername(String username);
}
实现UserDetailsService接口并overried loadUserByUsername类,返回userdetails.User 用来交给security做授权
userService
package com.dandan.service;
import com.dandan.dao.UserDao;
import com.dandan.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 java.util.ArrayList;
import java.util.List;
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
UserEntity user = userDao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
//用户权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (null != user.getRoles() && !user.getRoles().isEmpty()) {
String[] roles = user.getRoles().split(",");
for (String role : roles) {
if (null != role && !role.isEmpty()) {
authorities.add(new SimpleGrantedAuthority(role.trim()));
}
}
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
写个controller用来跳路由,
package com.dandan.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
//登陆页面
@RequestMapping("toLogin")
public String toLogin(){
return "views/login";
}
@RequestMapping("level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/" + id;
}
@RequestMapping("level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/" + id;
}
}
SpringSecurity配置类,继承WebSecurityConfigurerAdapter,开启WebSecurity(@EnableWebSecurity)
package com.dandan.config;
import com.dandan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人都可以访问
//其他页面需要对应角色
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2");
//没有权限重定向到登陆页面
http.formLogin().loginPage("/toLogin");
//关闭scrf(才可以使用get登出)
http.csrf().disable();
//开启注销功能
//后跳到首页
http.logout().logoutSuccessUrl("/");
//开启记住我功能,session和cookie实现,默认两周时间
http.rememberMe().rememberMeParameter("rememberme");
}
//springboot2.0.x可以直接使用,再高版本需要passwordEncoder,对应的数据库存用户密码的时候应该加密过
//There is no PasswordEncoder mapped for the id "null"
//在spring security5.0+新增了很多加密方法
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//从内存,使用这个认证方式,就不用啥啥service、dao、mysql
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("dandan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2")
// .and()
// .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2");
//从自定义service,编码方式bcrypt,
//不写encoder在比较新的springboot种会报错There is no PasswordEncoder mapped for the id "null",
//同样的数据库种存储的用户密码要用同种方式加密
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
}
注意:使用userService这种方式,需要在角色前加上ROEL_,不然授权不到。
结果:使用dandan用户登陆成功,并可以访问http://localhost:8080/level1/1
试图访问
http://localhost:8080/level2/1
项目已经集成thymeleaf,配合前端可以实现对相应角色显示相应内容(后端狗 不会写thymeleaf,抄的页面就不写出来了)