0%

springboot security解决cors

添加配置文件

1
2
3
4
5
6
7
@Configuration
public class CorsConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}

WebSecurityConfig extends WebSecurityConfigurerAdapter类里面添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
CorsConfigurationSource corsConfigurationSource()
{
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()....
}

tomcat配置cors(废弃)

官网

1
2
3
/data/tomcat/bin/catalina.sh version
>Using CATALINA_HOME: /data/tomcat/
vim /data/tomcat/conf/web.xml

添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<!-- 如果设置*,cors.support.credentials不能设置true不然会启动报错 -->
<!--设置成前端的访问地址例如 访问http://192.168.101.210:8006,这里就可以设置成该地址 -->
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>
<init-param>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.preflight.maxage</param-name>
<param-value>10</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

err:

1
Access to XMLHttpRequest at 'http://192.168.101.210:8005/fun/login.do?loginName=admin&pwd=a123456&yzm=2giu&userType=1' from origin 'http://127.0.0.1:32768' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

其中from origin后面的地址设置到cors.allowed.origins,多个地址,分割

问题:这个经测试,发现option一直提示跨域

filter解决跨域

新建一个CorsFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.xxx.controller.filter;

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


public class CorsFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

String currentOrigin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", currentOrigin); // 允许所有域名的请求
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
response.setHeader("Access-Control-Allow-Headers",
"User-Agent,Origin,Cache-Control,Content-type,Date,Server,withCredentials,AccessToken");
response.setHeader("Access-Control-Expose-Headers", "CUSTOMSESSIONID");
response.setHeader("Access-Control-Request-Headers", "CUSTOMSESSIONID");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-auth-token,Origin,Access-Token,X-Requested-With,Content-Type,Accept");
response.setHeader("Access-Control-Allow-Credentials", "true");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(200);
return;
}

chain.doFilter(req, res);
}

@Override
public void destroy() {

}
}

然后在src\main\webapp\WEB-INF\web.xml里配置,配成第一个过滤器

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.xxx.controller.innercs.filter.CorsFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

这个和tomcat不能同时配置,会导致同时设置两个跨域地址

跨域测试

chrome console输入

1
2
3
4
5
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://192.168.1.230:14083/app/geography/painting/',true);
xhr.setRequestHeader('Authorization', 'Bearer ...'); #带请求头
xhr.send();
xhr.send('body数据'); #带参数

在network查看结果

跨域分析

通过xhr请求的时候,response Header会返还Access-Control-Allow-Origin: *,通过界面和postman不会返回Access-Control-Allow-Origin: *,经分析测试,只有request Header中有该参数Origin: ,才会返回Access-Control-Allow-Origin: *。源代码位置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class DefaultCorsProcessor implements CorsProcessor {

private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class);


@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {

if (!CorsUtils.isCorsRequest(request)) {
return true;
}

ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
if (responseHasCors(serverResponse)) {
logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
return true;
}
....
------------------------------------------
public abstract class CorsUtils {

/**
* Returns {@code true} if the request is a valid CORS one.
*/
public static boolean isCorsRequest(HttpServletRequest request) {
return (request.getHeader(HttpHeaders.ORIGIN) != null);
}

参考

九种跨域方式实现原理

SpringBoot thymelef的使用以及模版

thymelef模版配置,可以不用配置

1
2
3
4
5
6
7
8
9
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
check-template-location: true
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
  1. thymelef添加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  2. resource/templates添加网页

    ${error}接收参数,th:action="@{/userLogin}"表单提交地址

    1
    2
    3
    4
    5
    6
    <p th:if="${error}" class="bg-danger" th:text="${error}"></p>
    <form method="post" class="layui-form" th:action="@{/userLogin}" >
    <input name="username" placeholder="用户名" >
    <input name="password" lay-verify="required" placeholder="密码">
    <input value="登录" id="login" style="width:100%;" type="submit">
    </form>
  3. 添加controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Controller
    public class LoginControl {
    @RequestMapping(value = "/login")
    public String login(Model model)
    {
    model.addAttribute("error","跳转而已"); //设置往网页传参数
    return "login"; //跳转login.html,并接受上面的参数
    }
    @RequestMapping(value = "/userLogin", method = RequestMethod.POST)
    public String userLogin(User user, Model model) {
    model.addAttribute("error", "出现异常");
    return "login"; //更新网页返回值
    }
    }

layui前端样式模版

直接复制到resource/static资源目录即可

  1. layui简单的post请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <button class="layui-btn"  lay-submit="" lay-filter="addsort">增加</button>
    <script>
    layui.use(['layer', 'form'], function() {
    var layer = layui.layer //引用layer模块
    , form = layui.form;//引用form模块
    form.on('submit(addsort)', function (data) { //addsort监听提交按钮
    console.log(data.elem) //被执行事件的元素DOM对象,一般为button对象
    console.log(data.form) //被执行提交的form对象,一般在存在form标签时才会返回
    console.log(data.field) //当前容器的全部表单字段,名值对形式:{name: value}
    $.ajax({
    type: 'POST',
    url: '/img/addSort',
    dataType: 'json',
    contentType: 'application/json',
    data: JSON.stringify(data.field),
    success:function (result) {
    console.log(result);
    if (result.code==200){
    layer.msg('添加成功');
    }else {
    layer.msg(result.msg);
    }
    }
    });
    return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
    });
    });
    </script>
  2. 表单数据加载

    1
    2
    3
    4
    5
    @RequestMapping(value = "/feedback")
    public String feedback(Model model){
    model.addAttribute("feedBacks",feedBacks);
    return "feedback";
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <table class="layui-table">
    <thead>
    <tr>
    <th>ID</th>
    <th>用户名</th>
    </thead>
    <tbody>
    <tr th:each="feedBack:${feedBacks}">
    <td th:text="${feedBack.feedbackid}"></td>
    <td th:text="${feedBack.user.username}"></td>
    </tr>
    </tbody>
    </table>
  3. 含文件图片的表单混合一起提交,原理首先使用layui绑定按钮提交

    上传图片按钮设置不自动 auto: false提交,然后存到文件里,然后表单提交的时候触发图片上传,以及绑定表单参数,设计时最好避免一起提交,设计可以先传好图片,然后填图片地址,分两次请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <script>
    var uploadInst;
    var pathObj; //存文件
    layui.use(['layer', 'form'], function () {
    var layer = layui.layer
    , form = layui.form;
    form.on('submit(addpaper)', function (data) {
    if (pathObj == undefined) {
    layer.msg("没有选择壁纸");
    return;
    }
    var sortid= $("select option:checked").attr("id");
    //选了文件直接改参数上传
    uploadInst.config.data = {
    paperdetail:data.field.paperdetail,
    sortid:sortid,
    papername:data.field.papername
    };
    uploadInst.config.auto = true;
    uploadInst.upload();
    return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
    });
    });

    layui.use(['layer', 'upload'], function () {
    var layer = layui.layer
    , upload = layui.upload;
    //普通图片上传
    uploadInst = upload.render({
    elem: '#post-photo'
    , url: '/img/uploadpaper'
    , auto: false
    , before: function (obj) {
    //预读本地文件示例,不支持ie8
    obj.preview(function (index, file, result) {
    $('#demo1').attr('src', result); //图片链接(base64)
    });
    },
    choose: function (object) {
    pathObj = object;
    }
    , done: function (res) {
    if (res.code == 200) {
    layer.msg("添加壁纸成功");
    } else {
    layer.msg("添加壁纸失败:" + res.msg);
    }
    //上传成功
    }
    });
    });
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @PostMapping("/uploadpaper")
    public Msg singleFileUpload(@RequestParam("file") MultipartFile file, @RequestParam("sortid") int sortid, @RequestParam("paperdetail") String paperdetail, @RequestParam("papername") String papername) {
    List<String> urls = new ArrayList<>();
    if (file == null) {
    return ResultUtil.error(-300, "上传文件为空");
    }
    Paper paper = new Paper();
    paper.setSortid(sortid);
    paper.setPapername(papername);
    paper.setPaperdetail(paperdetail);
    try {
    Files.write(Paths.get(uploadPath + file.getOriginalFilename()), file.getBytes());
    paper.setPaperurl(imgUrl + file.getOriginalFilename());
    paperMapper.insertPaper(paper);

    } catch (Exception e) {
    e.printStackTrace();
    return ResultUtil.error(-301, e.getMessage());
    }
    return ResultUtil.success();
    }
  4. 动态读取下拉框

    1
    2
    3
    <select name="sortid" id="sortid">
    <option th:each="arrayS:${sorts}" th:text="${arrayS.sortname}" th:id="${arrayS.sortid}">默认选项</option>
    </select>
    1
    2
    3
    4
    5
    6
    7
    @RequestMapping(value = "/addwallpaper")
    public String addwallpaper(Model model){
    List<Sort> sorts;
    sorts = paperMapper.selectSort();
    model.addAttribute("sorts",sorts);
    return "addwallpaper";
    }

springboot 基础

注解

@CrossOrigin 跨域处理

json处理

springboot集成jackson工具

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//忽略编译该参数为json    
@JsonIgnore
public String getPassword() {
return password;
}
//bean to json
ObjectMapper mapper = new ObjectMapper();
User user=new User();
String jsonstr=mapper.writeValueAsString(user);

@RestController //返回实体类自动转json
public class UserControl {
@RequestMapping(value = "register", method = RequestMethod.POST)
public Msg userReg(@RequestBody User user) { //@RequestBody 请求参数为json自动转换实体类
return user;
}
}

架构模型(Spring boot+vue+Spring Security

SpringSecurity

是一个安全认证权限控制等spring框架依赖注入原理(ioc)

简单用于登陆校验
  1. 添加maven依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  1. 新建User实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class User implements UserDetails {
    private int userid;
    private String username;
    private String password;
    //get,set省略
    @Override
    public String getUsername() {
    return username;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
    return true; //改为true
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
    return true; //改为true,不然账号是锁定的
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
    return true; //改为true
    }
    @JsonIgnore
    @Override
    public boolean isEnabled() {
    return true; //改为true,不然账号是禁用的
    }
    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
    }
    @JsonIgnore
    @Override
    public String getPassword() {
    return password;
    }
    }
  2. 新建UserMapper接口连接数据库

    1
    2
    3
    4
    5
    @Mapper
    public interface UserMapper {
    @Select("select * from user where username=#{username}")
    User loadUserByUsername(String username);
    }
  3. 新建UserService用户服务,注意实现UserDetailsService接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Service
    public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    User user = userMapper.loadUserByUsername(s);
    if (user == null) {
    throw new UsernameNotFoundException("用户名不对");
    }
    return user;
    }
    }
  4. 新建UserUtil工具类用户获取登陆后的用户信息

    1
    2
    3
    4
    5
    public class UserUtils {
    public static User getCurrentHr() {
    return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
    }
  5. 最重要的一步继承该类WebSecurityConfigurerAdapter,并配置,添加注解标明是配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
    @Override
    public void configure(WebSecurity web) throws Exception {
    //解决静态资源被拦截的问题,下面的忽略拦截,下面的路径不会走安全验证
    web.ignoring().antMatchers("/index.html", "/static/**", "/login_p","/user/register");
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());//配置用户登陆服务,并配置密码加密方式,这里可以自定义加密方式
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    .antMatchers("/admin/**")..hasRole("超级管理员")//改地址需要超管
    .anyRequest().authenticated()//其他地址的访问均需验证权限
    .and().formLogin()
    .loginPage("/login_p")//指定登录页是"/login_p",输入其他地址会替跳转到该页面
    .loginProcessingUrl("/login")
    .usernameParameter("username").passwordParameter("password")
    .permitAll() //指定登陆接口,只能用表单,如果要json请求要添加过滤器
    .failureHandler(new AuthenticationFailureHandler()) //登陆失败回调
    .successHandler(new AuthenticationSuccessHandler()) //登陆成功回调
    .and().logout().permitAll()
    .and().csrf().disable(); //解决非thymeleaf的form表单提交被拦截问题
    }
    }
  6. 登陆成功会自动有cookie返回,清除cookie然后测试都成功了

角色权限控制

这里简单实用,没有添加角色表,user表机构

username password role
admin *** admin
zhangsan *** user
  1. 在用户实体类重写getAuthorities该方法,该方法读取用户角色并存储返回到角色数组,如果是分表,role可以存储多个依次添加即可

    1
    2
    3
    4
    5
    6
    7
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    List<GrantedAuthority> authorities = new ArrayList<>();
    //“ROLE_”由于数据库未添加,所以这里手动添加
    authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
    return authorities;
    }
  2. 然后在配置里添加,意思是antMatchers里的地址只能是admin角色返回

    1
    2
    http.authorizeRequests()
    .antMatchers("/flower/imgUpload", "/flower/ddClass").hasRole("admin") //这两个地址需要管理员角色
  3. 现在已经能控制角色权限了,但是权限不足返回不友好,这里设置权限不足,返回提示信息

    1
    2
    3
    4
    5
    6
    .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    log.info("权限不足");
    }
    });
菜单角色管理

主要方法是设置过滤器

  1. 在配置文件添加

    1
    2
    3
    4
    5
    6
    7
    8
    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    @Override
    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
    o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
    o.setAccessDecisionManager(urlAccessDecisionManager);
    return o;
    }
    })
  2. 添加urlFilterInvocationSecurityMetadataSource

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @Component
    public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    MenuService menuService;
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
    //获取请求地址
    String requestUrl = ((FilterInvocation) o).getRequestUrl();
    if ("/login_p".equals(requestUrl)) {
    return null;
    }
    List<Menu> allMenu = menuService.getAllMenu();
    for (Menu menu : allMenu) {
    if (antPathMatcher.match(menu.getUrl(), requestUrl)&&menu.getRoles().size()>0) {
    List<Role> roles = menu.getRoles();
    int size = roles.size();
    String[] values = new String[size];
    for (int i = 0; i < size; i++) {
    values[i] = roles.get(i).getName();
    }
    return SecurityConfig.createList(values);
    }
    }
    //没有匹配上的资源,都是登录访问
    return SecurityConfig.createList("ROLE_LOGIN");
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
    return null;
    }
    @Override
    public boolean supports(Class<?> aClass) {
    return FilterInvocation.class.isAssignableFrom(aClass);
    }
    }
  3. 添加urlAccessDecisionManager

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Component
    public class UrlAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException {
    Iterator<ConfigAttribute> iterator = collection.iterator();
    while (iterator.hasNext()) {
    ConfigAttribute ca = iterator.next();
    //当前请求需要的权限
    String needRole = ca.getAttribute();
    if ("ROLE_LOGIN".equals(needRole)) {
    if (authentication instanceof AnonymousAuthenticationToken) {
    throw new BadCredentialsException("未登录");
    } else
    return;
    }
    //当前用户所具有的权限
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    for (GrantedAuthority authority : authorities) {
    if (authority.getAuthority().equals(needRole)) {
    return;
    }
    }
    }
    throw new AccessDeniedException("权限不足!");
    }
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
    return true;
    }
    @Override
    public boolean supports(Class<?> aClass) {
    return true;
    }
    }

常见问题

  1. 输出错误User account is locked

    解决:用户类设置返回true,原因自带安全机制,如果需要实现登陆错误次数,此处根据逻辑修改,并在user表添加该字段,通过数据库记录设置是否锁定。

    1
    2
    3
    4
    @Override
    public boolean isAccountNonLocked() {
    return true;
    }
  2. 错误No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken

    解决:WebSecurityConfigurerAdapter没有配置注册userService,而且必须对密码加密,这里必须设置加密方式,可以new PasswordEncoder实现自定加密

    1
    2
    3
    4
     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }
  3. 错误Encoded password does not look like BCrypt原因不识别数据库密码,有可能密码字段太短,或者被截断,另一种就是通过网页手动加密,复制进数据库也有可能不识别,解决通过代码加密插入解决

    1
    2
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    String encode = encoder.encode(user.getPassword());

参考

lenve/vhr

详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/flower/imgUpload", "/flower/ddClass").hasRole("admin") //这两个地址需要管理员角色
//其他地址的访问均需验证权限
.anyRequest().authenticated()
.and().formLogin()
//指定登录页是"/login"
.loginPage("/login_p")
.loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll()
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("登陆失败");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonResult;
if (e instanceof UsernameNotFoundException ){
jsonResult=e.getMessage();
}else if( e instanceof BadCredentialsException) {
jsonResult="密码输入错误,登录失败!";
} else {
jsonResult="登录失败!";
log.error(e.getMessage());
}
jsonResult=mapper.writeValueAsString(ResultUtil.error(-201,jsonResult));
out.write(jsonResult);
out.flush();
out.close();
}
}).successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("登陆成功");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonResult=mapper.writeValueAsString(ResultUtil.success(UserUtils.getCurrentHr()));
out.write(jsonResult);
out.flush();
out.close();
}
})
.and().logout().permitAll()
.and().csrf().disable() //解决非thymeleaf的form表单提交被拦截问题
.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
log.info("登陆失败");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonResult;
jsonResult="权限不足";
jsonResult=mapper.writeValueAsString(ResultUtil.error(-201,jsonResult));
out.write(jsonResult);
out.flush();
out.close();
}
});

斐讯K2 22.4.6.3 非telnet 页面直刷 Breed 详细方法(图文)

  1. 用网线连接电脑和路由器的lan口

  2. 保持路由器与internet的连通(桥接,有线都可以)

  3. 进入k2管理页面(p.to)

  4. 进入网页调试模式(chrome按F12),找到定时重启下拉05分的标签

  5. 在05的标签右键编辑

  6. 将“05”修改成为“05 | wget http://breed.hackpascal.net/breed-mt7620-phicomm-psg1208.bin”之后,鼠标移动到黑框外的空白处点击鼠标左键,结束编辑。

  7. 回到页面重新选择05,05后面多了刚刚加的,然后保存。

  8. 继续将“05”修改成“05 | mtd unlock Bootloader”,然后回到页面选择05保存。

  9. 继续将“05”修改成“05 | mtd -r write breed-mt7620-phicomm-psg1208.bin Bootloader”,然后回到页面选择05保存,等待重启,如果未重启代表失败。

  10. 拔除K2上Wan口的网线,路由器断电,持续按住路由器上的reset按钮,接通路由器电源,3秒后松开reset按钮。

  11. 在浏览器地址栏输入“http://192.168.1.1”访问Breed Web。

    注意:下次刷机只需进入brend ,步骤为

    1. 路由器断电,持续按住路由器上的reset按钮,接通路由器电源,3秒后松开reset按钮。
    2. 在浏览器地址栏输入“http://192.168.1.1”访问Breed Web。

    [RT-AC54U-GPIO-1-PSG1218-64M_3.4.3.9-099.trx](F:\xuan install.Back\K2.Back) 访问ip:http://192.168.123.1/ 管理账号:admin/admin wifi密码:1234567890

    高级设置-外部网络-外网连接类型pppoe拨号,用户名密码输入宽带账号密码,闪讯拨号插件选择重庆,其他默认,然后应用本页设置
    高级设置-系统管理-ntp服务器 分别填入红岩网校的ntp服务器202.202.43.120 202.202.43.131 手动设置时间 应用本页设置
    网络地图-点击地球的图标-重新连接 应该可以上网了

    参考http://www.tieba.com/p/5015549863

方法2

斐讯K2 V22.5.9.163官方固件定制版,集成breed,支持官版直刷【V1.4】

直接手动升级k2_163_v14_breed.bin然后进入breed

配置文件

配置文件路径resuources\application.properties或者resuources\application.yml二者存一

  1. application.propertiesapplication.yml区别

    一个是树形目录,一个是单行配置。eg:server.port=8080等效于

    1
    2
    server:
    port: 8080
  2. 配置环境变量

    1
    2
    3
    4
    diy:
    parmars: hello
    #配置文件引用say=hello
    say: ${diy.parmars}

    代码里面引用

    1
    2
    @Value("${diy.parmars}")
    private String imgUrl;​

常见配置

  1. 数据库连接配置

    1
    2
    3
    4
    5
    6
    spring:
    datasource:
    url: jdbc:mysql://10.14.0.1:3306/sqlname?useUnicode=true&characterEncoding=utf-8
    username: xxx
    password: xxxx
    driver-class-name: com.mysql.jdbc.Driver
  2. springboot访问端口配置

    1
    2
    server:
    port: 8080
  3. 静态文件路径映射配置,如果配置了spring.resources.static-location会覆盖原来默认的静态文件设置classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/file路径指本地磁盘路径,window/Mac/linux的真实目录路径,例如window为C:\\windos\\

    1
    2
    3
    spring:
    resources:
    static-locations: <原来的静态文件配置>,file:/Users/xuanleung/Pictures/
  4. 文件上传大小限制(-1不限制)(128KB)

    1
    2
    3
    4
    5
    spring: 
    servlet:
    multipart:
    max-file-size: -1
    max-request-size: -1

application.ymlbootstrap.yml区别

加载顺序bootstrap.yml ->bootstrap-xxx.yml->application.yml->application-xxx.yml

注意:多个配置文件,相同替换,不同并集

bootstrap.yml常用于一些系统级别参数,不被更改

application.yml 应用级别,可以被更改,通过config service服务

bootstrap.yml常用于应用程序引导阶段例如

  • spring config server配置
  • application.name配置

使用config server时,bootstrap.yml常用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: service-a
cloud:
config:
uri: http://127.0.0.1:8888
fail-fast: true
username: user
password: ${CONFIG_SERVER_PASSWORD:password}
retry:
initial-interval: 2000
max-interval: 10000
multiplier: 2
max-attempts: 10

在不考虑加载顺序,两个配置是可以通用的

多环境配置

可以在配置文件(application/bootstrap)名后加上dev、test等后缀以-分开。使用环境时加上参数--spring.profiles.active=peer1即可

eg: 新建个application-xxx.yml,运行时执行java -jar app.jar --spring.profiles.active=xxx

注意

这里如果存在多个环境配置文件,且有application/bootstrap没有后缀的原文件时会优先加载它,然后再用指定环境的配置覆盖没有后缀的,这样就会导致,如果没有后缀的文件指定了某个配置,但是有后缀的却没有设置改配置,覆盖的情况,就是取并集,导致某些设置不生效。

常见问题

  1. springboot存储中文到mysql数据库乱码

    解决:在配置文件连接数据库地方添加url: jdbc:mysql://<ip>:<端口>/<数据库名字>?useUnicode=true&characterEncoding=utf-8

  2. application.yml添加注释报错,提示找不到Failed to load property source from location 'classpath:/application.yml'

    解决:检查文件编码格式,不是utf-8修改为utf-8

  3. eureka高可用时 提示不可用unavailable-replicas ,原因是存在application.yml,且里面设置了

    1
    2
    3
    client:
    register-with-eureka: false #Eureka默认也会作为客户端尝试注册,因此需禁用注册行为
    fetch-registry: false

    然后application-peer.yml并没有设置改属性,取并集之后导致,禁止注册了

    解决:

    1. 修改application-peer.yml里的属性,并设置为true(未测试)

    2. 删除或重命名application-peer.yml(采用)

    3. application-peer.yml覆写配置,并设置为true(测试,能注册,但是还是unavailable-replicas

      解决:检查是否application.name是否设置了不一样的值,一定要设置一样的名字

参考

Springboot 之 静态资源路径配置

spring cloud unavailable-replicas

docker 挂载卷应用

基础知识

官网说明Use bind mounts

官网compose文件:Compose file version 3 reference

bind 挂载,就是宿主机路径对应容器路径,该挂载需要先创建目录,简称/path:/path

volume挂载,不需要提前创建目录,他是以volume形式,简称volume-name:/path

其中zZ的使用,eg:/path:/path:z

z小写的是可以容器共享

Z大写的是私有,容器不可共享

ro只读挂载

思路

利用gluterfs作为分布式文件系统做同步用,利用虚拟机挂载一个volume专门的存储硬盘

实现

常用命令
1
2
3
4
5
6
7
8
9
10
11
fdisk -l #查看磁盘
#mount [-参数] [设备名称] [挂载点]
mount /dev/sdb /mnt/test
#umount [设备名称或挂载点]
umount mnt/test
#gluster卷状态[status\start\stop\delete]
gluster volume status v-portainer
#节点添加与删除[probe\detach]
gluster peer detach home
#节点连接状态,没有卷的情况容易端口连接,删除添加节点可解决
gluster peer status
单步步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mkdir /dockerdata #创建数据存储盘
#虚拟机挂载该磁盘,作为存储,因为阿里云没有多余磁盘,所有不挂载,直接存到/dockerdata
mount /dev/sdb /dockerdata
df -h #查看是否挂载成功
#设置开机自动挂在该目录
echo "dev/sdb /dockerdata ext4 defaults 0 0">>/etc/fstab
mkdir /dockerdata/v-portainer
#创建分布式存储卷,force忽略在root目录创建挂在卷的警告
gluster volume create v-portainer replica 2 home:/dockerdata/v-portainer xuanps:/dockerdata/v-portainer force
#启动分布式存储卷
gluster volume start v-portainer
#创建挂载目录
mkdir /volume/v-portainer
#挂载存储卷目录,似乎挂载才会同步
mount -t glusterfs home:/v-portainer /volume/v-portainer
#设置开机挂载,两台单独设置
echo "home:/v-portainer /volume/v-portainer glusterfs defaults 0 1" >> /etc/fstab
#部署应用
docker stack deploy -c docker-compose.yml gitlab
问题总结
  1. 使用之后,存储数据受网络印象,导致部分应用因为长时间连接不上而导致,不能启动,因此搁置
  2. 如果出现节点disconnect,在disconnect节点执行systemctl restart glusterd重启

介绍

**Gluster**是一个大尺度文件系统。

主要功能
简单卷
  1. distribute volume 分布式卷,两台主机的磁盘融合一个磁盘
  2. stripe volume 条带卷,一个文件分成数据块存储到不同的地方
  3. replica volume 复制卷,一个文件分别保存到两台主机
复合卷

1+2,1+3,2+3,1+2+3

总结常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gluster peer status     #查看集群各主机连接状态
gluster volume list #查看挂载卷信息
gluster volume list #查看卷列表
#创建挂在卷,force忽略在root目录创建挂在卷的警告
gluster volume create swarm-volume replica 3 worker:/xuan/docker/gluster-volume home:/xuan/docker/gluster-volume xuanps:/xuan/docker/gluster-volume force
gluster volume start swarm-volume #启动
gluster volume stop swarm-volume #停止
gluster volume delete swarm-volume #删除 ,了文件还会保留
#挂载本地目录到glusterfs卷(swarm-volume),在本地目录添加的会自动同步到其他挂载卷
#eg在本机mnt添加文件,其他volume-name目录也会添加mount [-参数] [设备名称] [挂载点]
mount -t glusterfs worker:/swarm-volume /mnt/
umount worker:/swarm-volume #卸载了就不会同步了
#重置,删除所有数据
systemctl stop glusterd
rm -rf /var/lib/glusterd/
systemctl start glusterd
#删除节点
gluster peer detach home

安装

准备工作:

三台局域网主机(centos7 修改主机名

hostname ip 备注
xuanps 10.14.0.1
worker 10.14.0.4
home 10.14.0.5

三台都需要安装GlusterFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#搜索glusterfs可安装的版本
yum search centos-release-gluster
#安装最新长期稳定版本(Long Term Stable)的gluster软件
yum -y install centos-release-gluster
#安装glusterfs-server
yum --enablerepo=centos-gluster*-test install glusterfs-server
glusterfs -V #测试
systemctl enable glusterd #开机启动
systemctl start glusterd #启动
systemctl status glusterd #查看是否正常运行
#修改hosts不然不能通过主机名连接到对方
vim /etc/hosts
#----------三台都要添加如下设置--------------------------
10.14.0.1 xuanps
10.14.0.4 worker
10.14.0.5 home
#------------------------------------------------------
#从xuanps上执行下面两条,其他主机不用执行
gluster peer probe worker
gluster peer probe home
#三台都执行该命令是否都是connected
gluster peer status
#查看挂载卷信息
gluster volume info
#创建挂在卷,force忽略在root目录创建挂在卷的警告
gluster volume create volume-name replica 3 worker:/xuan/docker/gluster-volume/test home:/xuan/docker/gluster-volume/test xuanps:/xuan/docker/gluster-volume/test force
#启动
gluster volume start volume-name
#启动nfs同步,测试需验证,这里要不要开启
gluster volume set volume-name nfs.disable off
#挂载本地目录到glusterfs卷(volume-name),在本地目录添加的会自动同步到其他挂载卷
#eg在本机mnt添加文件,其他volume-name目录也会添加
mount -t glusterfs worker:/volume-name /mnt/

参考

官网:https://www.gluster.org/

文档

centos官方安装手册

基于 GlusterFS 实现 Docker 集群的分布式存储

docker 安装docker-volume-glusterfs

前提,首先安装好GlusterFS分布式文件系统,可以参考centos7 安装 GlusterFS

sapk/docker-volume-gluster安装(弃)

不足:无法删除volume,且无法复用

1
2
3
4
5
6
7
8
9
10
#安装插件,三台主机都安装(保险起见)
docker plugin install sapk/plugin-gluster
# docker volume create --driver sapk/plugin-gluster --opt voluri="<volumeserver>,<otherserver>,<otheroptionalserver>:<volumename>" --name test
#volumeserver 主机名,可以指定多个,volumenam是 GlusterFS文件系统的挂载劵名,test是swarm挂载卷名
docker volume create --driver sapk/plugin-gluster --opt voluri="worker,home,xuanps:swarm-volume" --name test
#运行ubuntu容器进行测试
docker run -v test:/mnt --rm -ti ubuntu
#进去之后创建文件,其他系统盘也能看到该文件了, 但是其他系统不会创建挂载卷
echo "hello">/mnt/testfile

docker-compose 使用

1
2
3
4
5
volumes:
some_vol:
driver: sapk/plugin-gluster
driver_opts:
voluri: "<volumeserver>:<volumename>"

calavera/docker-volume-glusterfs官方安装(废弃 测试时发现运行找不到插件,太老了,换新插件)

1
2
3
4
5
6
7
8
9
#---------------------------废弃,网络原因下载不下来--------------------------------
#为了环境干净,安装docker golang容器工具(--rm参数,运行后销毁容器)
docker run -v /tmp/bin:/go/bin \
--rm golang go get github.com/golang/example/hello/...
#测试,执行该命令会输出Hello, Go examples!,如果没输出,说明容器环境和主机环境不一致
/tmp/bin/hello
#----------------准备工作完成正式开始安装---------------------------------------------------------
#通过golang容器工具下载插件,下载到/tmp/bin目录
docker run -v /tmp/bin:/go/bin --rm golang go get github.com/calavera/docker-volume-glusterfs

手动去下载,通过sftp等工具传到服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
mv docker-volume-glusterfs_linux_amd64 docker-volume-glusterfs   #重命名
cp ./docker-volume-glusterfs /usr/bin #放到bin目录
chmod 777 /usr/bin/docker-volume-glusterfs #添加权限
#其他两台主机,然后复制到其他服务器上
scp root@10.14.0.1:~/docker-volume-glusterfs ~
#依次放到bin目录添加权限
cp ~/docker-volume-glusterfs /usr/bin
chmod 777 /usr/bin/docker-volume-glusterfs
# 三台主机都执行该命令,该命令会前端运行
docker-volume-glusterfs -servers xuanps:worker:home
# 测试使用
sudo docker run --volume-driver glusterfs --volume swarm-volume:/data alpine ash
touch /data/helo

额外的 docker 卷插件

官方插件列表

官方插件商店

rancher/convoy

主要功能快照、备份、还原

总结:适用于单节点,单主机,的本地卷管理

calavera/docker-volume-glusterfs

centos7 安装 GlusterFS

Pure Storage Docker Volume Plugin

Hedvig Docker Volume Plugin

依赖于hedvig cluster

REX-Ray

github: rexray/rexray

参考:每天5分钟玩转 OpenStack Rex-Ray

………..居然没找到一个合适的提供者

rexray/cis-nfs 待测试,NFS似乎不满足需求

参考

Docker与Golang的巧妙结合

基于 GlusterFS 实现 Docker 集群的分布式存储

Docker 应用之owncloud

官网:library/owncloud

默认会创建挂载卷-v /<mydatalocation>:/var/www/html

细分挂载卷

  • -v /<mydatalocation>/apps:/var/www/html/apps installed / modified apps
  • -v /<mydatalocation>/config:/var/www/html/config local configuration
  • -v /<mydatalocation>/data:/var/www/html/data the actual data of your ownCloud (网盘文件)

安装

1
2
3
4
5
docker pull owncloud
#默认安装,会默认创建一个挂载卷
docker run -d -p 14007:80 owncloud:8.1
#将网盘文件存储指向到oss,注意挂载目录的权限问题,否则没权限操作会报错
docker run -v /ossfs/owncloud:/var/www/html/data -d -p 14007:80 owncloud:latest

体验

虽然成功存储到了oss里面但是极度卡,就算是阿里云内网ossfs,一样的卡,因此放弃存到ossfs,可以考虑备份放到ossfs