SpringBoot整合SpringSecurity(基于内存和基于数据库)
环境搭建->springBoot2.3.4+mysql5.6+security5.x+druid1.1.13
一基于内存用户认证
1.1环境依赖
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--新增-->
<!-- 数据库连接池druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.13</version>
</dependency>
<!--新增-->
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--用于支持非严格html-->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<!--导入校验的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<!--导入配置文件处理器,配置文件在进行绑定的时候有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.从内存中读取
编写自己的配置类 MySecurityConfig1 继承WebSecurityConfigurerAdapter,重写3个方法:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler; //认证成功处理类
@Autowired
MyAuthenctiationFailureHandler myAuthenctiationFailureHandler; //认证失败处理类
/****
* 数据库
* ------> 实现UserDetailsService 重写->loadUserByUsername方法
* @param auth
* @throws Exception
*/
@Autowired
SecurityUserDetailsService securityUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 1.从内存中读取
auth.inMemoryAuthentication().passwordEncoder
(new BCryptPasswordEncoder()).withUser("user")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("USER");
// //2.从数据库读取
// auth.userDetailsService(securityUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
/****
* 授权-----------> loginPage("/login") 路径必须跟登入表单的路径一致
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() /* 需要授权的请求 */
.antMatchers("/login","/home").permitAll() /* 过滤不需要认证的路径 */
.anyRequest().authenticated() /* 对任何一个请求,都需要认证 */
.and() /* 完成上一个配置,进行下一步配置 */
//.httpBasic();
.formLogin() /* 配置表单登录 */
.loginPage("/login") /* 设置登录页面 */
.successHandler(myAuthenctiationSuccessHandler) /* 设置成功处理器 */
.failureHandler(myAuthenctiationFailureHandler) /* 设置失败处理器*/
.and()
.logout() /* 登出 */
.logoutSuccessUrl("/home"); /* 设置退出页面 */
// 关闭CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
2.Controller层
@Controller
public class LoginController1 {
@RequestMapping("/home")
public String showHome() {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println(name);
return "home";
}
@RequestMapping("/login")
public String showLogin() {
return "login2";
}
3.前台页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>登录</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js" th:src="@{/js/jquery-2.2.3.min.js}"></script>
<script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js" th:src="@{/js/popper.min.js}"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</head>
<script th:inline="javascript">
var app = new Vue({
el: '#app',
data: {
project: [[${session.error}]]
}
});
app['']
</script>
<body>
<div class="container">
<div class="row" style="margin-top: 20px;">
<div class="col-md-3">
<h2>登陆</h2>
<form th:action="@{/login}" method="post">
<div class="form-group">
<label for="username">username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
</div>
<div class="form-group">
<label for="Password">password:</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Enter password">
</div>
<div class="form-group" th:if="${param.error}">
<!--<p th:if="${session.SPRING_SECURITY_LAST_EXCEPTION}">-->
<!--<p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></p>-->
<p th:text="${session.error['msg']}"></p>
<p th:id="messages1"></p>
<!--<p style="color: red">账户名密码错误!</p>-->
<!--</p>-->
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>
4.定义自定义失败处理类,
/****
* 定义自定义失败处理类,
*/
@Component("myAuthenctiationFailureHandler")
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//看自己的选择
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
logger.info("进入认证失败处理类" ,exception.getMessage());
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
Map<String,String> sb=new HashMap<>();
sb.put("status","error");
if (exception instanceof UsernameNotFoundException || exception instanceof BadCredentialsException) {
sb.put("msg","用户名或密码输入错误,登录失败!");
} else if (exception instanceof DisabledException) {
sb.put("msg","账户不可用");
} else {
sb.put("msg","授权失败!");
}
HttpSession session = request.getSession();
session.setAttribute("error",sb);
response.sendRedirect("/security/login?error=true");
}
}
5.定义自定义成功处理类,
@Component("myAuthenctiationSuccessHandler")
public class MyAuthenctiationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
logger.info("登录成功");
response.setContentType("application/json;charset=UTF-8");
/****
* 我的是-->直接回写在页面
* 这里可以作一些自己的业务逻辑
* -> 1.比如 登入成功页面跳转到主页
* -> 2.比如 重定向到某个页面,把成功信息带过去等等
*
*/
response.getWriter().write( objectMapper.writeValueAsString(authentication));
}
}
实现效果
成功登入页面显示:
失败页面显示
二基于数据库用户认证
2.1实现UserDetailsService 重写->loadUserByUsername方法
/****
* 数据库
* ------> 实现UserDetailsService 重写->loadUserByUsername方法
* @param auth
* @throws Exception
*/
@Autowired
SecurityUserDetailsService securityUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// // 1.从内存中读取
// auth.inMemoryAuthentication().passwordEncoder
// (new BCryptPasswordEncoder()).withUser("user")
// .password(new BCryptPasswordEncoder().encode("123456"))
// .roles("USER");
//2.从数据库读取
auth.userDetailsService(securityUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
2.2securityUserDetailsService类的详情信息
@Service("userDetailsService")
public class SecurityUserDetailsService implements UserDetailsService {
@Autowired
private UserServiceImpl userService;
@Autowired
private RoleServiceImpl roleService;
@Autowired
private PermissionServiceImpl permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = userService.findByUserName(username);
if(userInfo == null)
{
throw new UsernameNotFoundException("用户名不存在!");
}
//处理自己的用户对象封装成UserDetails ,
User user=new User(userInfo.getUsername(),userInfo.getPassword(),userInfo.getStatus()==0?false:true,true,true,true,getAuthority(userInfo.getRoles()));
return user;
}
//返回一个List集合,Role集合装入权限代码
public List<SimpleGrantedAuthority> getAuthority(List<Role> roles){
List<SimpleGrantedAuthority> list=new ArrayList();
for (Role role:roles){
list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
}
return list;
}
}
三 其他配置信息
其他配置信息
Druid配置类
@Configuration
public class DruidConfig {
// 配置数据源
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){
return new DruidDataSource();
}
// 配置Druid的监控
// 配置一个管理后台的servlet
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","");//默认就是允许所有访问
initParams.put("deny","192.168.50.36");
bean.setInitParameters(initParams);
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
去除druid底部的广告配置类
/**
* 类名称:RemoveDruidAdConfig
* 类描述: 去除druid底部的广告配置类
*
* Version 1.0
*/
@Configuration
@ConditionalOnWebApplication
@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true)
public class RemoveDruidAdConfig {
/**
* 方法名: removeDruidAdFilterRegistrationBean
* 方法描述: 除去页面底部的广告
* @param properties
* @return org.springframework.boot.web.servlet.FilterRegistrationBean
* @throws
*/
@Bean
public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
//创建filter进行过滤
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
@Override
public void destroy() {
}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}
yml配置
server:
port: 8081
servlet:
context-path: /security
#数据源 start->
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# //下面是数据库的属性配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
#filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
SQL 5张表:用户表-角色表 --中间表(用户-角色)—>一对多关系
角色—资源表—中间表(角色–资源表)—> 多 对多关系
只显示部分sql 表
-- 用户表
CREATE TABLE users(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(32) UNIQUE NOT NULL,
username VARCHAR(32),
PASSWORD VARCHAR(255),
phoneNum VARCHAR(20),
STATUS INT
);
INSERT INTO users VALUES(2,'security@security.com','security','$2a$10$jj36gqUKsto7kirff5sd2OyaiYGAqcdj/pSn.zdBHdM1EJ94r9qCq','qwsa242526',1);
-- 角色表
CREATE TABLE role(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
roleName VARCHAR(50),
roleDesc VARCHAR(50)
);
INSERT INTO role VALUE(1,"ADMIN","可以用任何权限");
INSERT INTO role VALUE(2,"USER","有部分权限");
项目地址: gitHubspringBoot整合springSecurity(基于内存和数据库用户认证!)